Blog de Amazon Web Services (AWS)

Cómo automatizar y optimizar el uso de AWS Transfer Family para la creación de servidores SFTP

Por Hugo Dominguez, Arquitecto de Soluciones en Amazon Web Services para Sector Público

Introducción

AWS Transfer Family es un servicio gestionado por AWS que nos permite escalar de forma segura las transferencias de archivos recurrentes de empresa a empresa a los servicios de almacenamiento de AWS mediante los protocolos SFTP, FTPS, FTP y AS2.

AWS Transfer Family permite a nuestros clientes modernizar el flujo de ingesta de archivos por protocolos tradicionales hacia almacenamiento en nube, mediante Amazon S3 y Amazon EFS. Al ser un servicio gestionado, AWS maneja el lanzamiento y creación del servidor de transferencia de datos. Dicho servidor permanece activo durante el ciclo de vida.

Con el objetivo de optimizar los costos asociados al consumo del servicio, veremos cómo es posible automatizar la creación y eliminación de un servidor.

Descripción de la Solución

Imagen 1: Arquitectura de la solución

Imagen 1: Arquitectura de la solución

Como se observa en la Imagen 1, la solución está basada en un despliegue completamente Serverless que cuenta con los siguientes componentes:

Amazon S3 es un servicio de almacenamiento de objetos que ofrece escalabilidad, disponibilidad de datos, seguridad y rendimiento líderes del sector. AWS Transfer Family accede a su bucket de Amazon S3 para atender las solicitudes de transferencia de sus usuarios.

Amazon Route 53 es un servicio web de Sistema de Nombres de Dominio (Por sus siglas en inglés – DNS) escalable y de alta disponibilidad. Usaremos este servicio para poder tener una dirección de dominio SFTP fija para que nuestros usuarios finales no deban reconfigurar el Host de SFTP cada vez que creamos un servidor SFTP mediante AWS Transfer Family. Para la solución se usó un dominio administrado por Amazon Route 53, en el caso que su dominio no sea administrado por Amazon Route 53, por favor siga esta guía.

AWS Lambda es un servicio informático que permite ejecutar código sin aprovisionar ni administrar servidores. Las funciones AWS Lambda dentro de la arquitectura se encargarán de la creación de recursos mediante el AWS SDK para Python – Boto3.

AWS Step Function es un servicio de orquestación sin servidor que le permite integrarse con Funciones AWS Lambda y otros servicios de Amazon Web Services. AWS Step Function orquestará la creación de recursos ejecutados mediante funciones AWS Lambda.

Amazon EventBridge es un servicio sin servidor que utiliza eventos para conectar los componentes de la aplicación, lo que facilita la creación de aplicaciones escalables basadas en eventos. Para tomar medidas en relación con los eventos recibidos por Amazon EventBridge crearemos una regla mediante una expresión cron, en la cual configuraremos como destino (Target) el servicio de AWS StepFunction. Esta regla nos ayudará a programar la ejecución del flujo de trabajo en AWS StepFunction.

AWS Transfer Family es un servicio de transferencia segura que le permitirá transferir archivos dentro y fuera de los servicios de almacenamiento de AWS. AWS Transfer Family gestionará el servidor con protocolo de transferencia de archivos SFTP.

AWS System Manager Parameter Store es una capacidad de AWS System Manager que proporciona un almacenamiento seguro y jerárquico para la administración de los datos de configuración y secretos. Usaremos este servicio para guardar el ServerId asociado a AWS Transfer Family y las llaves SSH de los usuarios del servidor SFTP.

Amazon DynamoDB es un servicio de base de datos NoSQL totalmente administrado que ofrece un rendimiento rápido y predecible, así como una perfecta escalabilidad. En Amazon DynamoDB guardaremos los usuarios asociados al servidor SFTP (AWS Transfer Family).

Creación de la solución: Paso a paso

Creación de Tabla de Amazon DynamoDB

Crearemos la tabla de Amazon DynamoDB que guardará los datos de nuestros usuarios, usaremos como clave de partición el nombre de nuestra organización y clave de clasificación el nombre de usuario, adicional a ello tendremos el atributo ssh que guardará el nombre del ParameterStore donde se almacenará la llave SSH.

En la consola de AWS buscaremos el servicio de Amazon DynamoDB y crearemos una tabla.

Usaremos como nombre de la tabla “aws-transferfamily-usuarios”, clave de partición “empresa” y clave de clasificación “usuario” (Imagen 2).

Imagen 2: Creación de tabla DynamoDB

Imagen 2: Creación de tabla DynamoDB

En la Configuración de la tabla, seleccionaremos Configuración personalizada y seleccionaremos en la Configuración de capacidades On-demand (Imagen 3). Finalmente crearemos la tabla.

Imagen 3: Continuación – Creación de tabla DynamoDB

Creación de Parameter Store

Ahora crearemos un ParameterStore en AWS System Manager, para ello buscaremos System Manager en la consola de AWS y seleccionaremos Parameter Store (Imagen 4). Luego crearemos un Parámetro con el siguiente nombre “/transferfamily/server/id”, en el valor usaremos “demo” de forma temporal. Recordemos que este valor será actualizado cada vez que creemos un nuevo servidor SFTP de AWS Transfer Family (Imagen 5).

Imagen 4: AWS System Manager – Parameter Store

Imagen 5: Creación de Parameter Store

Repetiremos el procedimiento de creación de parámetros dos veces más, creando así los parámetros asociados a las llaves SSH de los usuarios del servidor SFTP en AWS Transfer Family.

Nombres: “/transferfamily/users/usuario1”, “/transferfamily/users/usuario2”, quedando la configuración similar a la siguiente imagen (Imagen 6).

Imagen 6: Resultado esperado de la creación del Parameter Store.(No se ofusco el Hash intencionalmente porque tiene valores que no son reales)

Crear usuarios en Amazon DynamoDB

Ahora iremos a Amazon DynamoDB (Imagen 7) nuevamente para insertar nuestros dos usuarios configurados anteriormente en AWS System Manager – Parameter Store.

Imagen 7: Creación de tabla de Amazon DynamoDB

Ahora crearemos el Ítem para el usuario1 y usuario2, donde el valor del atributo empresa será “aws” para ambos usuarios, para el atributo usuario será “usuario1” y “usuario2” respectivamente”, adicionalmente se creará un atributo de cadena / String con nombre ssh, cuyo valor será el nombre del AWS System Manager – Parameter Store asociado al usuario (Imagen 8 y 9).

Imagen 8: Creación de Items en la tabla de Amazon DynamoDB

Imagen 9: Resultado esperado de la creación de Items en la tabla de Amazon DynamoDB

Creación de Bucket en Amazon S3

Ahora crearemos el bucket en Amazon S3 que atenderá las solicitudes de transferencia, para ello buscaremos S3 en la consola de AWS y crearemos un Bucket. Para este blog vamos a usar el nombre de “aws-transferfamily-demo-25022023” y dejaremos las demás opciones con los valores por defecto, tener en cuenta que el nombre del bucket es único por lo que deberá usar un nombre distinto (Imagen 10).

Imagen 10: Creación de bucket de Amazon S3

Creación de Politicas y Roles de IAM

Ahora crearemos las políticas y roles para las cinco funciones AWS Lambda mostradas en la arquitectura. Funciones: crear-servidor-sftp, actualizar-parameter-store, crear-cname, buscar-usuarios, crear-usuarios.

Ahora indicaremos el paso a paso para crear una política en AWS. Debemos ir a la consola de AWS y buscar el servicio de IAM, una vez en el servicio seleccionaremos la opción de Políticas / Crear Política. Elegiremos la definición de la politica mediante JSON y pegaremos el siguiente contenido. Tener en cuenta que debemos reemplazar ${ACCOUNT_ID} por el ID de nuestra cuenta.

Daremos dos veces Siguiente y en la parte final de Revisión le daremos un nombre a nuestra política, para este caso usaremos:

politica-crear-servidor-sftp

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents",
                "transfer:CreateServer"
            ],
            "Resource": [
                "arn:aws:iam::${ACCOUNT_ID}:role/service-role/AWSTransferLoggingAccess",
                "arn:aws:transfer:us-east-1:${ACCOUNT_ID}:server/*",
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*"
            ]
        }
    ]
}

Ahora repetiremos los pasos de crear una política para las demás funciones, siendo la descripción superior a la política el nombre de la misma.

politica-actualizar-parameter-store

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ssm:PutParameter",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:ssm:us-east-1:${ACCOUNT_ID}:parameter/transferfamily/*",
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*"
            ]
        }
    ]
}

politica-crear-cname

Para este caso reemplazaremos ${HOSTED_ZONE_ID} por el ID de la zona alojada en Amazon Route53.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/${HOSTED_ZONE_ID}",
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*"
            ]
        }
    ]
}

politica-buscar-usuarios

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "dynamodb:Query",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*",
                "arn:aws:dynamodb:us-east-1:${ACCOUNT_ID}:table/aws-transferfamily-usuarios"
            ]
        }
    ]
}

Antes de crear la política asociada a la Creación de usuarios, deberemos crear la política y el rol para los usuarios, en este caso la política se llamará politica-aws-transferfamily-usuarios. En este caso se está dando permisos de escritura en la ubicación raíz del bucket, así como los permisos para ubicar y listar los objetos dentro del Bucket (El nombre del bucket variará según lo que usted haya creado).

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::aws-transferfamily-demo-25022023"
            ],
            "Effect": "Allow",
            "Sid": "ReadWriteS3"
        },
        {
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::aws-transferfamily-demo-25022023/*"
            ],
            "Effect": "Allow",
            "Sid": ""
        }
    ]
}

Ahora procederemos a Crear el Rol, para ello nos ubicamos el servicio de IAM y seleccionamos Roles, Crear Rol. Elegimos como uso a Lambda y damos siguiente (Imagen 11).

Imagen 11: Creación de Rol IAM para usuarios

Ahora buscamos la política que acabamos de crear (politica-aws-transferfamily-usuarios) y la seleccionamos, luego damos siguiente.

Imagen 12: Agregar permisos al Rol IAM para usuarios

Finalmente le ponemos un nombre a nuestro rol antes de crearlo. (rol-aws-transferfamily-usuarios).Luego de haber creado el rol, buscamos el rol y editamos la relación de confianza – Trust relationships, editamos (Edit trust policy) y reemplazamos la politica por lo siguiente:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "transfer.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Con esta configuración estamos otorgando la confianza a AWS Transfer Family para que pueda otorgar acceso a Amazon S3. Ahora vamos con la politica-crear-usuarios, como podemos ver esta política incluye el rol que creamos líneas arriba dando los permisos al bucket destino.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameter",
                "iam:PassRole",
                "transfer:CreateUser",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:iam::${ACCOUNT_ID}:role/rol-aws-transferfamily-usuarios",
                "arn:aws:transfer:us-east-1:${ACCOUNT_ID}:server/*",
                "arn:aws:ssm:us-east-1:${ACCOUNT_ID}:parameter/transferfamily/*",
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*"
            ]
        }
    ]
}

Ahora procederemos a crear los roles asociados a las políticas creadas, excepto para la política politica-aws-transferfamily-usuarios. Para esto seguiremos la guía de creación de rol que se detalló líneas arriba. Creando un rol asociado a la política, de la siguiente manera:

  • politica-crear-servidor-sftp > rol-crear-servidor-sftp
  • politica-actualizar-parameter-store > rol-actualizar-parameter-store
  • politica-crear-cname > rol-crear-cname
  • politica-buscar-usuarios > rol-buscar-usuarios
  • politica-crear-usuarios > rol-crear-usuarios

Imagen 13: Asociación de Políticas a Roles de IAM

Creación de Funciones AWS Lambda

Ahora pasaremos a crear las Funciones AWS Lambda dentro de la Arquitectura: crear-servidor-sftp, actualizar-parameter-store, crear-cname, buscar-usuarios, crear-usuarios.

Para ello iremos a la consola de AWS y buscaremos Lambda, una vez en el servicio de AWS Lambda crearemos una función. Usaremos la configuración por defecto y nombraremos a la función según la lista anterior, iniciaremos con crear-servidor-sftp, tener en cuenta la elección del Runtime Python 3.9 y el rol asociado al nombre de la función, para este caso rol-crear-servidor-sftp (Imagen 14). Luego se crearán las demás funciones Lambda con el mismo proceso.

Imagen 14: Creación de la Función Lambda

Para esta función Lambda crearemos una variable de entorno con nombre loggingRole y valor arn:aws:iam::${ACCOUNT_ID}:role/service-role/AWSTransferLoggingAccess. Para esto ubicaremos la pestaña de Configuración, Variables de entorno, luego Editar. Ingresamos la llave y el valor asociado y guardamos (Imagen 15 y 16).

Imagen 15: Variables de entorno de la función Lambda

Imagen 16: Creación de la variable de entorno de la Función Lambda

Código de la función crear-servidor-sftp

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

transferfamily = boto3.client('transfer')

# Get Environment Variables
loggingRole = os.environ['loggingRole']

def lambda_handler(event, context):
    try:
        server = transferfamily.create_server(
            EndpointType='PUBLIC',
            LoggingRole=loggingRole,
            Protocols=['SFTP'])
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot start the creation server') from error
    return {
            'serverId': server['ServerId']
            }

Función Lambda actualizar-parameter-store, para esta función Lambda crearemos una variable de entorno con nombre parameterServerId y valor /transferfamily/server/id mediante el proceso antes indicado.

Código de la función actualizar-parameter-store

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

ssm = boto3.client('ssm')

# Get Environment Variables
parameterServerId = os.environ['parameterServerId']

def lambda_handler(event, context):
    try:
        parameter = ssm.put_parameter(
            Name=parameterServerId,
            Value=event['serverId'],
            Overwrite=True)
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot update the parameter store') from error
    return "Success"

Función Lambda crear-cname, para esta función Lambda crearemos cuatro variables de entorno con los siguientes nombres y valores mediante el proceso antes indicado:

  • Nombre hostedZoneId, valor HOSTED_ZONE_ID (Reemplazar por ID de la zona alojada).
  • Nombre recordName, valor DOMINIO_SFTP (Reemplazar por el dominio para el servidor SFTP, por ejemplo sftp.ejemplo.com).
  • Nombre recordTTL, valor
  • Nombre region, valor us-east-1.

Código de la función crear-cname

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

route53 = boto3.client('route53')

# Get Environment Variables
hostedZoneId = os.environ['hostedZoneId']
recordName = os.environ['recordName']
recordTTL = os.environ['recordTTL']
region = os.environ['region']

transferServer = '.server.transfer.'+region+'.amazonaws.com'

def lambda_handler(event, context):
    print(event)
    try:
        record = route53.change_resource_record_sets(
            HostedZoneId=hostedZoneId,
            ChangeBatch={
                'Comment': 'Record Set related to SFTP Server / Transfer Family',
                'Changes': [
                    {
                        'Action': 'CREATE',
                        'ResourceRecordSet': {
                            'Name': recordName,
                            'Type': 'CNAME',
                            'TTL': int(recordTTL),
                            'ResourceRecords': [
                                {
                                    'Value': event['serverId']+transferServer
                                },
                            ]
                        }
                    },
                ]
            })
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot create the record Route 53') from error
    return "Success"

Función Lambda buscar-usuarios, para esta función Lambda crearemos una variable de entorno con nombre tableName y valor aws-transferfamily-usuarios mediante el proceso antes indicado.

Código de la función buscar-usuarios

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

dynamodb = boto3.client('dynamodb')

# Get Environment Variables
tableName = os.environ['tableName']

def lambda_handler(event, context):
    try:
        users = dynamodb.query(
                TableName=tableName,
                KeyConditionExpression='empresa = :id',
                ExpressionAttributeValues={
                    ':id': {'S': 'aws'}
                }
            )
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot search dynamoDB users') from error
    return {
            'serverId': event['serverId'],
            'users': users['Items']
            }

Función Lambda crear-usuarios, para esta función Lambda crearemos dos variables de entorno con los siguientes nombres y valores mediante el proceso antes indicado:

  • Nombre accessRole, valor arn:aws:iam::${ACCOUNT_ID}:role/rol-aws-transferfamily-usuarios.
  • Nombre homeDirectory, valor /aws-transferfamily-demo-25022023/.

Código de la función crear-usuarios

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

ssm = boto3.client('ssm')
transferfamily = boto3.client('transfer')

# Get Environment Variables
accessRole = os.environ['accessRole']
homeDirectory = os.environ['homeDirectory']

def lambda_handler(event, context):
    try:
        serverId = event['serverId']
        users = event['users']
        
        for item in users:
            userName = item['usuario']['S']
            parameterSSH = item['ssh']['S']
            
            ssh_key = ssm.get_parameter(
            Name=parameterSSH)
            
            transferfamily.create_user(
            HomeDirectory=homeDirectory,
            Role=accessRole,
            ServerId=serverId,
            SshPublicKeyBody=ssh_key['Parameter']['Value'],
            UserName=userName)
            
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot add new users') from error
    return "Success"

Creación de AWS Step Functions para orquestación

Creación de AWS Step Function, buscaremos el servicio en la consola de AWS y crearemos una Máquina de estado, Diseñaremos el flujo de trabajo de forma visual y seleccionamos siguiente (Imagen 17).

Imagen 17: Creación de Step Function

Diseñaremos el flujo de trabajo, arrastrando y soltando primero AWS Lambda: Invoke del panel izquierdo, luego configuraremos en el panel derecho el nombre del estado como Crear Servidor SFTP (Imagen 18), en el nombre de la función asociada buscaremos la función Lambda que creamos. (crear-servidor-sftp).

Imagen 18: Creación de Step Function – Crear Servidor SFTP

Seguidamente en el panel izquierdo seleccionaremos la pestaña de Flujo / Flow y arrastraremos y soltaremos el estado Paralelo / Parallel debajo de nuestra primera función Lambda configurada.

Ahora en el panel izquierdo seleccionaremos la pestaña Acciones y arrastraremos y soltaremos tres AWS Lambda: Invoke y cada de uno de estos estará asociado a la función Lambda correspondiente, tal como se observa en la Imagen 19:

Imagen 19: Creación de Step Function – Procesamiento Paralelo y Funciones Lambda

Finalmente añadiremos un AWS Lambda: Invoke debajo de nuestro estado Buscar usuarios y lo asociaremos a la función Lambda correspondiente, como se observa a continuación (Imagen 20):

Imagen 20: Creación de Step Function – Crear usuarios con una Función Lambda

Dejaremos todo por defecto y haremos dos veces clic a siguiente, finalmente le pondremos un nombre a nuestra máquina de estados, el cuál será creacion-recursos-transfer-family y activaremos los registros de log con nivel ALL y crearemos la Maquina de estados (Imagen 21).

Imagen 21: Creación de Step Function – Configuración de State Machine

Configuración de Amazon Event Bridge

Finalmente configuraremos una regla en el servicio Amazon EventBridge, para ellos buscaremos EventBridge en la consola de AWS y seleccionaremos la opción de Reglas, luego crearemos una regla (Imagen 22).

Imagen 22: Creación de regla en Amazon EventBridge

Como nombre de la regla se tendrá crear-recursos-transfer-family y el tipo de regla será Schedule, continuaremos con la definición del Schedule (Imagen 23). Para este blog usaremos una programación diaria a ejecutarse a las 12 horas UTC. (0 12 * * ? *), luego seleccionaremos siguiente.

Imagen 23: Creación de la regla de Amazon EventBridge – Schedule Pattern

Ahora asociaremos la regla con la máquina de estados AWS Step Function que creamos anteriormente, para ello seleccionamos el destino AWS Services y buscamos Step Functions state machine, luego seleccionamos la máquina de estados correspondiente, dejaremos que se cree un nuevo rol y damos siguiente, siguiente y creamos la regla (Imagen 24). Con esto hemos concluido la creación de recursos de forma programada.

Imagen 24: Creación de la regla de Amazon EventBridge – Schedule Pattern

En este paso, hemos creado un servidor SFTP en el primer estado de la Maquina de Estados, este estado tiene como salida el ID del servidor (ServerId). Con el ServerId, los tres estados que se ejecutan en paralelo hacen el siguiente proceso:

  • Actualizar Parameter Store: Actualizará el Parameter Store donde se almacena el ServerId, esto a fin de que pueda ser utilizado cuando se elimine el servidor SFTP – AWS Transfer Family.
  • Crear CNAME: Creará el registro en Amazon Route 53, usando el ServerId de AWS Transfer Family.
  • Buscar Usuarios: Buscará los usuarios registrados en DynamoDB para poder enviarlos al siguiente estado Crear Usuarios, junto con el ServerId.

Eliminación automatizada de recursos de la instancia de AWS Transfer Family

Imagen 25: Arquitectura de la solución – Automatizar la eliminación de los servidores de AWS Transfer Family

Creación de políticas y roles de IAM

Crearemos las políticas y roles para las tres funciones AWS Lambda mostradas en la arquitectura. El proceso de la creación de la política será el mismo que se explicó para la creación de recursos, recordar que debemos reemplazar ${ACCOUNT_ID} por el ID de nuestra cuenta.

Funciones: obtener-serverid, eliminar-servidor, eliminar-cname.

politica-obtener-serverid

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameter",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*",
                "arn:aws:ssm:*:${ACCOUNT_ID}:parameter/transferfamily/*"
            ]
        }
    ]
}

politica-eliminar-servidor

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "transfer:DeleteServer",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:transfer:*:${ACCOUNT_ID}:server/*",
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*"
            ]
        }
    ]
}

politica-eliminar-cname

Para este caso reemplazaremos ${HOSTED_ZONE_ID} por el ID de la zona alojada en Amazon Route53.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:${ACCOUNT_ID}:*",
                "arn:aws:route53:::hostedzone/${HOSTED_ZONE_ID} "
            ]
        }
    ]
}

Ahora procederemos a crear los roles asociados a las políticas creadas. Para esto seguiremos la guía de creación de rol que se detalló líneas arriba. Creando un rol asociado a la política, de la siguiente manera:

  • politica-obtener-serverid > rol-obtener-serverid
  • politica-eliminar-servidor > rol-eliminar-servidor
  • politica-eliminar-cname > rol-eliminar-cname

Creación de funciones AWS Lambda

Ahora pasaremos a crear las Funciones AWS Lambda dentro de la Arquitectura. Nos guiaremos del proceso de creación de funciones Lambda que se detalló líneas arriba y estarán relacionadas con el rol asociado al nombre de la función. Para la función obtener-serverid crearemos una variable de entorno con nombre parameterServerId y valor /transferfamily/server/id mediante el proceso antes indicado.

Código de la función obtener-serverid

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

ssm = boto3.client('ssm')

# Get Environment Variables
parameterServerId = os.environ['parameterServerId']

def lambda_handler(event, context):
    try:
        serverId = ssm.get_parameter(
            Name=parameterServerId)
            
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot get the parameter store') from error
    return {
            'serverId': serverId['Parameter']['Value']

Código de la función eliminar-servidor

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

transferfamily = boto3.client('transfer')

def lambda_handler(event, context):
    try:
        transferfamily.delete_server(
            ServerId=event['serverId'])
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot delete the server') from error
    return "Success"

Para la función Lambda eliminar-cname crearemos cuatro variables de entorno con los siguientes nombres y valores mediante el proceso antes indicado:

  • Nombre hostedZoneId, valor HOSTED_ZONE_ID (Reemplazar por ID de la zona alojada).
  • Nombre recordName, valor DOMINIO_SFTP (Reemplazar por el dominio SFTP que eligió para el servidor, por ejemplo sftp.ejemplo.com).
  • Nombre recordTTL, valor
  • Nombre region, valor us-east-1.

Código de la función eliminar-cname

import boto3
import os
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

route53 = boto3.client('route53')

# Get Environment Variables
hostedZoneId = os.environ['hostedZoneId']
recordName = os.environ['recordName']
recordTTL = os.environ['recordTTL']
region = os.environ['region']

transferServer = '.server.transfer.'+region+'.amazonaws.com'

def lambda_handler(event, context):
    print(event)
    try:
        record = route53.change_resource_record_sets(
            HostedZoneId=hostedZoneId,
            ChangeBatch={
                'Changes': [
                    {
                        'Action': 'DELETE',
                        'ResourceRecordSet': {
                            'Name': recordName,
                            'Type': 'CNAME',
                            'TTL': int(recordTTL),
                            'ResourceRecords': [
                                {
                                    'Value': event['serverId']+transferServer
                                },
                            ]
                        }
                    },
                ]
            })
    except Exception as error:
        logger.exception(error)
        raise RuntimeError('Internal Error - cannot d the record Route 53') from error
    return "Success"

Creación de flujo en AWS Step Functions

Ahora diseñaremos nuestro flujo de trabajo en AWS Step Function, para ello crearemos un nuevo diseño con las indicaciones antes proporcionadas.

El primer estado AWS Lambda: Invoke tendrá como nombre Obtener ServerId y como función Lambda a obtener-serverid, luego de ello configuraremos un flujo paralelo para asociarlos a dos estados AWS Lambda: Invoke (Eliminar Servidor y Eliminar CNAME) cada uno asociado al Lambda respectivo.

Imagen 26: Creación de Step Function – Eliminar Servidor de AWS Transfer Family

Teniendo el siguiente resultado, procederemos a crear la Máquina de Estados cuyo nombre será eliminar-recursos-transfer-family. Finalmente configuraremos una regla en el servicio AWS EventBridge, para seguiremos los pasos mostrados anteriormente.

Como nombre de la regla se tendrá eliminar-recursos-transfer-family y el tipo de regla será Schedule, continuaremos con la definición del Schedule. Para este blog usaremos una programación diaria a ejecutarse a las 15 horas UTC. (0 15 * * ? *), y asociaremos la regla al destino / Target el servicio AWS Step Function state machine y seleccionaremos la máquina de estado eliminar-recursos-transfer-family.

Con esto hemos concluido la eliminación de recursos de forma programada.

En este paso, hemos buscado el ServerId (AWS System Manager Parameter Store) en el primer estado de la Maquina de Estados, este estado tiene como salida el ID del servidor (ServerId). Con el ServerId, los dos estados que se ejecutan en paralelo hacen el siguiente proceso:

  • Eliminar Servidor: Eliminará el servidor de AWS Transfer Family mediante el ServerId.
  • Eliminar CNAME: Eliminará el registro asociado al servidor de AWS Transfer Family.

Limpieza de recursos

  1. Eliminar la tabla de AWS DynamoDB:
  2. Navegar en la consola de AWS al servicio de DynamoDB
  3. Seleccionar la opción de Tables,seleccionar la tabla aws-transferfamily-usuarios y proceder a eliminar esta tabla autorizando la confirmación.
  4. Eliminar Bucket de Amazon S3:
  5. Navegar en la consola de AWS al servicio de S3.
  6. Buscar el bucket que se ha creado durante el despliegue de la solución, seleccionarlo y proceder eliminarlo autorizando la confirmación.
  7. Eliminar Funciones de AWS Lambda:
  8. Navegar en la consola de AWS al servicio de AWS Lambda.
  9. Buscar las funciones que se han creado durante el despliegue de la solución, seleccionarlas y proceder a eliminarlas.
  10. Eliminar Máquinas de estado de Amazon AWS Step Function:
  11. Navegar en la consola de AWS al servicio AWS Step Function.
  12. Buscar las máquinas de estado que se han creado durante el despliegue de la solución, seleccionarlas y proceder eliminarlas.
  13. Eliminar las Reglas de Amazon AWS EventBridge:
  14. Navegar en la consola de AWS al servicio AWS EventBridge.
  15. Buscar las reglas que se han creado durante el despliegue de la solución, seleccionarlas y proceder eliminarlas.
  16. Eliminar ParameterStore de Amazon AWS System Manager:
  17. Navegar en la consola de AWS al servicio AWS System Manager / Parameter Store.
  18. Buscar los parámetros que se han creado durante el despliegue de la solución, seleccionarlos y proceder eliminarlos.

Conclusiones

Con esta solución hemos aprovechado la capacidad que nos brinda AWS EventBridge para poder programar la ejecución de eventos junto con AWS StepFunction que orquesta la creación y eliminación de recursos asociados a AWS Transfer Family.

La solución optimiza los costos asociados al uso de AWS Transfer Family para casos en los cuales solo se necesita tener disponible el servidor por algunas horas.

Acerca del autor

Hugo Dominguez es Arquitecto de Soluciones en Amazon Web Services para Sector Público. Hugo ayuda a múltiples instituciones de educación en Latinoamérica en la adopción tecnológica aprovechando la plataforma de Amazon Web Services.

 

 

Revisores Técnicos

 

Gabriel Paredes es Arquitecto de Soluciones en Amazon Web Services para Sector Público. Gabriel ayuda a múltiples instituciones de educación en Latinoamérica en la adopción tecnológica y mejora de sus servicios estudiantiles.

 

 

 

Claudia Izquierdo es Arquitecta de Soluciones en Amazon Web Services para el Sector Público. Claudia ha ayudado a múltiples entidades de gobierno y organizaciones no gubernamentales en Latinoamérica a cumplir con sus misiones y objetivos de negocio. Le apasionan los temas de cloud, networking, seguridad y ciberseguridad de la información. Claudia también es una instructora Cisco premiada mundialmente y apoya en proyectos para más mujeres en tecnología