Ir al contenido
Acceso Just-In-Time (JIT) en AWS con Lambda e IAM Identity Center
Fotografía de Aron Visuals en Unsplash

Acceso Just-In-Time (JIT) en AWS con Lambda e IAM Identity Center

·1818 palabras·9 mins
AWS Aws Iam-Identity-Center Just-in-Time-Access Aws-Lambda Security
Sergio Cambelo
Autor
Sergio Cambelo
Arquitecto Cloud
Tabla de contenido

Cuando definimos la estrategia de gestión de accesos a AWS de una organización compleja, el principio de mínimo privilegio es innegociable. Sin embargo, los entornos no son estáticos, y aunque a priori un desarrollador no tiene por qué acceder al entorno de producción, cuando surge una incidencia crítica es necesario que ese desarrollador pueda acceder para resolverla de manera inmediata. Tradicionalmente hay dos enfoques para permitir ese acceso. El primero es tener los accesos ya pre-concedidos, asignando un Permission Set con permisos elevados. Esta solución va totalmente en contra del mínimo privilegio necesario, ya que supone un riesgo adicional. La otra solución implica que se configuren los accesos para el desarrollador en el momento, pero eso requiere de la disponibilidad del equipo encargado de administrar IAM Identity Center y requiere de la coordinación de más actores.

Esta segunda manera de proceder es la que se conoce como Just-in-Time (JIT), que consiste en conceder permisos elevados de manera temporal solo cuando son necesarios. En este artículo vamos a ver cómo generar una solución JIT totalmente automatizada usando servicios nativos de AWS como son AWS Lambda y el propio Identity Center.

Nota: Este artículo asume que el lector tiene conocimientos sobre AWS IAM Identity Center y está familiarizado con los servicios de AWS utilizados en la solución: Lambda, DynamoDB, EventBridge y SNS.

Qué es el acceso Just-in-Time (JIT)
#

Desde la perspectiva del Gobierno Cloud, el acceso Just-in-Time sirve como mecanismo de control y auditoría para garantizar que se proveen los mecanismos necesarios para operar la plataforma a la vez que se garantiza la seguridad y la trazabilidad.

De esta manera, es posible dotar a los equipos que trabajan en la plataforma de la libertad necesaria para llevar a cabo las tareas necesarias para el mantenimiento y operación de la misma, asegurando que se cumplen los criterios de seguridad y cumplimiento de la organización.

La implementación del acceso JIT aporta beneficios significativos para la seguridad y gestión de una organización:

  1. Reducción de la superficie de ataque: Al conceder permisos elevados solo cuando son necesarios y por tiempo limitado, se minimiza la exposición a posibles vulnerabilidades de seguridad.

  2. Cumplimiento del principio de mínimo privilegio: Los usuarios solo tienen acceso a los recursos que necesitan en el momento exacto que los requieren, eliminando permisos permanentes innecesarios.

  3. Trazabilidad y auditoría completa: Cada solicitud de acceso queda registrada con información detallada sobre quién, cuándo, por qué y durante cuánto tiempo se concedieron los permisos.

Arquitectura JIT con servicios nativos de AWS
#

Diagrama de arquitectura JIT mostrando el flujo desde la solicitud del usuario a través de AWS Lambda hasta IAM Identity Center. El proceso incluye: usuario solicita acceso elevado, Lambda procesa la petición, valida permisos, crea assignment temporal en Identity Center, usuario recibe acceso temporal, y finalmente Lambda revoca el acceso automáticamente
Arquitectura de la solución JIT automatizada utilizando AWS Lambda para gestionar los accesos temporales en IAM Identity Center

Componentes de la arquitectura
#

IAM Identity Center: Servicio centralizado de gestión de identidades y accesos que permite administrar usuarios, grupos y Permission Sets. En nuestra solución, gestiona los accesos temporales mediante Account Assignments dinámicos.

AWS Lambda - Función Grant: Invocada desde herramientas ITSM como ServiceNow, frontales personalizados o integraciones con Slack. Valida los parámetros de entrada, asocia el usuario en IAM Identity Center con la cuenta solicitada y el Permission Set de privilegios elevados, registra el resultado en la tabla de auditoría y almacena el estado del permiso concedido. El timestamp de caducidad se calcula añadiendo el período configurado (por ejemplo, 24 horas) a la fecha de solicitud, pudiendo ser dinámico si se especifica en la invocación.

AWS Lambda - Función Revoke: Ejecutada en intervalos regulares (por ejemplo, cada hora) mediante una regla cron de Amazon EventBridge. Consulta la tabla de estados de Amazon DynamoDB para obtener registros cuyo timestamp de caducidad haya sido superado, elimina las asociaciones usuario/cuenta/Permission Set expiradas y registra el resultado en la tabla de auditoría.

Amazon EventBridge: Servicio de bus de eventos que programa la ejecución de la función Revoke mediante reglas cron configurables (por ejemplo, cada hora).

Amazon DynamoDB - Tabla de Estado: Almacena el estado actual de los permisos JIT activos, incluyendo el usuario, la cuenta de destino y el timestamp de caducidad del permiso para gestionar la revocación automática.

Amazon DynamoDB - Tabla de Auditoría: Registra todas las peticiones de acceso privilegiado con información del usuario, cuenta de destino, fecha y hora de la solicitud, así como el resultado exitoso o fallido de cada operación.

Amazon SNS: Servicio de notificaciones que envía alertas al equipo de operaciones cuando ocurren errores en cualquiera de las funciones Lambda, permitiendo intervención manual cuando sea necesario.

Flujo de concesión de permisos (Grant)
#

  1. El usuario solicita acceso elevado a través de una herramienta ITSM (ServiceNow), frontal personalizado o integración con Slack. AWS Lambda Grant valida que todos los parámetros de entrada son correctos y procesa la solicitud.

  2. La función Lambda Grant crea la asociación temporal entre el usuario, la cuenta de destino y el Permission Set en IAM Identity Center.

  3. Se almacena el estado del permiso activo con el timestamp de caducidad calculado en la tabla de estado de Amazon DynamoDB.

  4. Se registra la operación en la tabla de auditoría de Amazon DynamoDB con información del usuario, cuenta, fecha/hora y resultado.

  5. En caso de error, se envía una notificación a través de Amazon SNS al equipo de soporte.

Flujo de revocación automática (Revoke)
#

  1. Amazon EventBridge ejecuta la función Lambda Revoke según la regla cron configurada (cada hora).

  2. AWS Lambda Revoke consulta la tabla de estado de Amazon DynamoDB para identificar registros cuyo timestamp de caducidad haya sido superado.

  3. Para cada permiso expirado, la función elimina la asociación usuario/cuenta/Permission Set en IAM Identity Center.

  4. Se actualiza la tabla de estado de Amazon DynamoDB eliminando los registros procesados.

  5. Se registra cada operación de revocación en la tabla de auditoría con el resultado de la operación.

  6. En caso de error durante el proceso, se envía notificación al equipo de soporte a través de Amazon SNS.

Gestión de errores
#

En caso de error en cualquiera de los flujos, ambas funciones Lambda envían una notificación a través de Amazon SNS al equipo de operaciones, permitiendo intervención manual para resolver la incidencia.

Implementación de las funciones Lambda
#

Nota importante: El código mostrado es únicamente de referencia para ilustrar los conceptos y flujos descritos en este artículo. No debe utilizarse directamente en un entorno de producción sin una revisión exhaustiva, adaptación a los requisitos específicos de seguridad de la organización, y pruebas completas. Se recomienda implementar validaciones adicionales, manejo de errores más robusto, logging detallado y cumplimiento de las políticas de seguridad corporativas.

Función Grant - Concesión de acceso temporal
#

La función Lambda Grant gestiona la asignación temporal de permisos AdministratorAccess a usuarios específicos. A continuación se muestra un código de ejemplo de como podría implementarse la lógica de la función.

import boto3
import json
import uuid
from datetime import datetime, timedelta

def lambda_handler(event, context):
    # Clientes AWS
    sso_admin = boto3.client('sso-admin')
    identitystore = boto3.client('identitystore')
    dynamodb = boto3.resource('dynamodb')
    sns = boto3.client('sns')
    
    # Parámetros de entrada
    user_email = event['user_email']
    account_id = event['account_id']
    duration_hours = event.get('duration_hours', 24)
    request_id = str(uuid.uuid4())
    
    try:
        # Obtener Identity Store ID e Instance ARN
        instances = sso_admin.list_instances()['Instances']
        instance_arn = instances[0]['InstanceArn']
        identity_store_id = instances[0]['IdentityStoreId']
        
        # Buscar usuario por email
        user_response = identitystore.list_users(
            IdentityStoreId=identity_store_id,
            Filters=[{
                'AttributePath': 'UserName',
                'AttributeValue': user_email
            }]
        )
        user_id = user_response['Users'][0]['UserId']
        
        # Buscar Permission Set AdministratorAccess
        permission_sets = sso_admin.list_permission_sets(
            InstanceArn=instance_arn
        )
        
        admin_permission_set_arn = None
        for ps_arn in permission_sets['PermissionSets']:
            ps_details = sso_admin.describe_permission_set(
                InstanceArn=instance_arn,
                PermissionSetArn=ps_arn
            )
            if ps_details['PermissionSet']['Name'] == 'AdministratorAccess':
                admin_permission_set_arn = ps_arn
                break
        
        # Crear account assignment
        sso_admin.create_account_assignment(
            InstanceArn=instance_arn,
            TargetId=account_id,
            TargetType='AWS_ACCOUNT',
            PermissionSetArn=admin_permission_set_arn,
            PrincipalType='USER',
            PrincipalId=user_id
        )
        
        # Calcular timestamp de expiración
        expiry_time = datetime.utcnow() + timedelta(hours=duration_hours)
        
        # Guardar estado en DynamoDB
        state_table = dynamodb.Table('jit-access-state')
        state_table.put_item(
            Item={
                'assignment_id': f"{user_id}#{account_id}#{admin_permission_set_arn}",
                'user_id': user_id,
                'account_id': account_id,
                'permission_set_arn': admin_permission_set_arn,
                'expiry_timestamp': int(expiry_time.timestamp()),
                'created_at': datetime.utcnow().isoformat()
            }
        )
        
        # Registrar en tabla de auditoría
        audit_table = dynamodb.Table('jit-access-audit')
        audit_table.put_item(
            Item={
                'request_id': request_id,
                'user_email': user_email,
                'account_id': account_id,
                'permission_set': 'AdministratorAccess',
                'action': 'GRANT',
                'status': 'SUCCESS',
                'timestamp': datetime.utcnow().isoformat(),
                'expires_at': expiry_time.isoformat()
            }
        )
        
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Acceso concedido exitosamente',
                'expires_at': expiry_time.isoformat()
            })
        }
        
    except Exception as e:
        # Registrar error en auditoría
        try:
            audit_table = dynamodb.Table('jit-access-audit')
            audit_table.put_item(
                Item={
                    'request_id': request_id,
                    'user_email': user_email,
                    'account_id': account_id,
                    'permission_set': 'AdministratorAccess',
                    'action': 'GRANT',
                    'status': 'ERROR',
                    'error_message': str(e),
                    'timestamp': datetime.utcnow().isoformat()
                }
            )
        except:
            pass
        
        # Enviar notificación de error por SNS
        try:
            sns.publish(
                TopicArn='arn:aws:sns:region:account:jit-access-errors',
                Subject='Error en función JIT Grant',
                Message=f'Error al conceder acceso JIT:\nUsuario: {user_email}\nCuenta: {account_id}\nError: {str(e)}'
            )
        except:
            pass
        
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

Función Revoke - Revocación automática de accesos
#

La función Lambda Revoke se ejecuta periódicamente para eliminar automáticamente los permisos JIT que han expirado:

import boto3
import json
import uuid
from datetime import datetime
from boto3.dynamodb.conditions import Attr

def lambda_handler(event, context):
    # Clientes AWS
    sso_admin = boto3.client('sso-admin')
    dynamodb = boto3.resource('dynamodb')
    sns = boto3.client('sns')
    
    current_timestamp = int(datetime.utcnow().timestamp())
    revoked_count = 0
    errors = []
    
    try:
        # Obtener Instance ARN
        instances = sso_admin.list_instances()['Instances']
        instance_arn = instances[0]['InstanceArn']
        
        # Consultar permisos expirados
        state_table = dynamodb.Table('jit-access-state')
        response = state_table.scan(
            FilterExpression=Attr('expiry_timestamp').lt(current_timestamp)
        )
        
        expired_assignments = response['Items']
        
        for assignment in expired_assignments:
            try:
                # Eliminar account assignment
                sso_admin.delete_account_assignment(
                    InstanceArn=instance_arn,
                    TargetId=assignment['account_id'],
                    TargetType='AWS_ACCOUNT',
                    PermissionSetArn=assignment['permission_set_arn'],
                    PrincipalType='USER',
                    PrincipalId=assignment['user_id']
                )
                
                # Eliminar de tabla de estado
                state_table.delete_item(
                    Key={'assignment_id': assignment['assignment_id']}
                )
                
                # Registrar revocación en auditoría
                audit_table = dynamodb.Table('jit-access-audit')
                audit_table.put_item(
                    Item={
                        'request_id': str(uuid.uuid4()),
                        'user_id': assignment['user_id'],
                        'account_id': assignment['account_id'],
                        'permission_set': 'AdministratorAccess',
                        'action': 'REVOKE',
                        'status': 'SUCCESS',
                        'timestamp': datetime.utcnow().isoformat(),
                        'reason': 'EXPIRED'
                    }
                )
                
                revoked_count += 1
                
            except Exception as e:
                error_msg = f"Error revocando {assignment['assignment_id']}: {str(e)}"
                errors.append(error_msg)
                
                # Registrar error en auditoría
                try:
                    audit_table = dynamodb.Table('jit-access-audit')
                    audit_table.put_item(
                        Item={
                            'request_id': str(uuid.uuid4()),
                            'user_id': assignment.get('user_id', 'unknown'),
                            'account_id': assignment.get('account_id', 'unknown'),
                            'permission_set': 'AdministratorAccess',
                            'action': 'REVOKE',
                            'status': 'ERROR',
                            'error_message': str(e),
                            'timestamp': datetime.utcnow().isoformat()
                        }
                    )
                except:
                    pass
        
        # Enviar notificación si hay errores
        if errors:
            try:
                sns.publish(
                    TopicArn='arn:aws:sns:region:account:jit-access-errors',
                    Subject='Errores en función JIT Revoke',
                    Message=f'Se encontraron {len(errors)} errores durante la revocación:\n\n' + '\n'.join(errors)
                )
            except:
                pass
        
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': f'Proceso completado. {revoked_count} permisos revocados, {len(errors)} errores',
                'revoked_count': revoked_count,
                'error_count': len(errors)
            })
        }
        
    except Exception as e:
        # Error general en la función
        try:
            sns.publish(
                TopicArn='arn:aws:sns:region:account:jit-access-errors',
                Subject='Error crítico en función JIT Revoke',
                Message=f'Error crítico en la función de revocación: {str(e)}'
            )
        except:
            pass
        
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

Para finalizar
#

La implementación de acceso Just-in-Time con servicios nativos de AWS representa un equilibrio perfecto entre seguridad y operatividad. Esta solución permite a las organizaciones mantener el principio de mínimo privilegio mientras proporcionan la flexibilidad necesaria para responder a incidencias críticas de manera inmediata y automatizada.

La combinación de AWS Lambda, IAM Identity Center, DynamoDB y EventBridge ofrece una arquitectura robusta, escalable y completamente gestionada que elimina la necesidad de herramientas externas o procesos manuales. Con la trazabilidad completa y las notificaciones automáticas, los equipos de seguridad mantienen el control total sobre los accesos privilegiados mientras los equipos operativos obtienen la autonomía necesaria para resolver incidencias críticas.

¿Quieres estar al día con las últimas tendencias en arquitectura cloud y seguridad en AWS? Suscríbete al boletín y recibe contenido exclusivo sobre mejores prácticas, casos de uso reales y soluciones innovadoras directamente en tu bandeja de entrada.

Suscríbete al boletín

Referencias
#

AWS IAM Identity Center:

Boto3 SDK:

Relacionados

Aplicación de controles preventivos en entornos multicuenta
·2123 palabras·10 mins
AWS Aws Organizations Security Governance Scp Iam Multi-Account Cloud Governance
Migración del tráfico de red de Transit Gateway a AWS Cloud WAN
·2111 palabras·10 mins
AWS Aws Cloud-Wan Transit-Gateway Networking Migration
Inspección de Seguridad Global con AWS Cloud WAN
·2157 palabras·11 mins
AWS Aws Aws-Cloudwan Network-Function-Groups Service-Insertion Aws-Network-Firewall