-
Notifications
You must be signed in to change notification settings - Fork 0
authorizer
Los comandos de authorizer en spa-cli permiten generar y registrar Lambda Authorizers personalizados. El handler resultante se ejecuta sin cambios en dos contextos:
-
Modo serverless: como Lambda real conectada a API Gateway vía
x-amazon-apigateway-authorizer. -
Modo container: como dependencia FastAPI (middleware) gracias al
auth_bridge.pygenerado durantespa project build --build-mode container.
Ver detalle completo del flujo container en lambda-authorizers.md.
El módulo authorizer automatiza dos cosas: (1) la creación del archivo de código del authorizer (basado en patrón Cognito Token + IAM policy) y (2) el registro de la entry correspondiente en spa_project.toml. Esto cierra el ciclo entre la declaración del securityScheme en api.yaml y la implementación real.
Genera un Lambda Authorizer y lo registra en la configuración del proyecto
spa authorizer add NAME [--role-name ROLE] [--lambda-name LAMBDA]| Parámetro | Descripción | Default | Requerido |
|---|---|---|---|
NAME (positional) |
Prefijo del authorizer. Debe matchear el securityScheme de api.yaml sin el sufijo _authorizer. Espacios y guiones se convierten a _. |
— | ✅ Sí |
--role-name |
Nombre del IAM role que ejecuta la Lambda. Se usa al construir el ARN en modo serverless. | <NAME>-authorizer-role |
No |
--lambda-name |
Nombre de la función Lambda authorizer. | <NAME>-authorizer |
No |
-
Normalización del nombre: convierte
-y espacios a_. -
Lectura de configuración: carga
spa_project.tomlvíaload_config(). Si falta, aborta. -
Generación del handler:
- Crea
src/authorizers/<NAME>/handler.pydesde el template internospa_cli/templates/authorizers/handler.py.txt. - Crea
__init__.pyvacíos ensrc/authorizers/ysrc/authorizers/<NAME>/si faltan (para que sean paquetes Python importables). - Skip silencioso si
handler.pyya existe (no sobreescribe trabajo).
- Crea
-
Registro en TOML: append de la sección:
[spa.api.lambda-authorizers.<NAME>] role_name = "<role-name>" lambda_name = "<lambda-name>" module = "src.authorizers.<NAME>.handler"
Skip si la sección ya existe.
src/authorizers/
├── __init__.py
└── <NAME>/
├── __init__.py
└── handler.py
spa_project.toml recibe la nueva entry de [spa.api.lambda-authorizers.<NAME>].
spa authorizer add principalResultado:
-
src/authorizers/principal/handler.pygenerado. - En
spa_project.toml:[spa.api.lambda-authorizers.principal] role_name = "principal-authorizer-role" lambda_name = "principal-authorizer" module = "src.authorizers.principal.handler"
spa authorizer add admin --role-name admin-auth-role --lambda-name admin-authorizer-fnspa authorizer add user
spa authorizer add admin
spa authorizer add apikeyEl template handler.py.txt produce código equivalente al patrón Cognito clásico:
def lambda_handler(event, context):
raw_token = event.get("authorizationToken")
if not raw_token:
return generate_policy("user", Effect.DENY, event.get("methodArn", "*"))
token = raw_token.replace("Bearer ", "").strip()
try:
token_data = validate_access_login(token) # cognito.get_user(AccessToken=token)
except CognitoServiceError:
return generate_policy("user", Effect.DENY, event.get("methodArn", "*"))
username = token_data.get("Username")
if not username:
return generate_policy("user", Effect.DENY, event.get("methodArn", "*"))
user_attributes = token_data.get("UserAttributes", [])
user_data = {"username": username}
for attr in user_attributes:
if not attr["Name"].startswith("custom:"):
user_data[attr["Name"]] = attr["Value"]
extra_info = {"user": json.dumps(user_data)}
resource = generate_resource_income(event.get("methodArn", "*"))
return generate_policy("user", Effect.ALLOW, resource, context=extra_info)boto3 se importa con try/except — el stub no truena en entornos sin AWS SDK (útil para tests locales).
Excepciones modeladas:
-
Exception("Unauthorized")→ API GW 401 / Bridge 401. -
Exception("MalformedToken")→ API GW 422 / Bridge 422. -
CognitoServiceError(interna, capturaClientError) → DENY policy → 403.
El nombre del comando se enlaza al securityScheme por convención estricta. Para spa authorizer add principal:
# api.yaml
components:
securitySchemes:
principal_authorizer: # NAME + "_authorizer"
type: apiKey
name: Authorization # Header donde llega el token
in: header # header | query | cookie
x-amazon-apigateway-authtype: custom
x-amazon-apigateway-authorizer:
type: token
identitySource: method.request.header.Authorization
authorizerUri: LAMBDA_ARN_PLACEHOLDER # build_api lo sustituye en serverless
authorizerCredentials: APIG_ROLE_ARN_PLACEHOLDER
authorizerResultTtlInSeconds: 60Endpoint protegido:
paths:
/users/{id}:
get:
security:
- principal_authorizer: []| Modo | Quién invoca lambda_handler
|
Origen del event
|
|---|---|---|
serverless |
API Gateway (autenticado por la Lambda real desplegada con Pulumi) | API Gateway construye el event TOKEN/REQUEST |
container |
auth_bridge.py (middleware FastAPI) |
_build_apigw_event(request, raw_token, path) arma un event APIGW REST v1 sintético desde el HTTP request |
El bridge lee securityScheme.in + securityScheme.name del openapi.json para extraer el token:
in |
Source en runtime |
|---|---|
header (default) |
request.headers[name] (ej. Authorization) |
query |
request.query_params[name] |
cookie |
request.cookies[name] |
| (fallback) | parsea x-amazon-apigateway-authorizer.identitySource
|
El bridge resuelve qué función importar en este orden:
-
moduleexplícito despa_project.toml(lo quespa authorizer addregistra por defecto). -
src.authorizers.<key>.handler.lambda_handler. - Legacy:
build.infra.components.authorizers.<key>.lambda_function.lambda_handler(compat con proyectos serverless históricos). -
infra.components.authorizers.<key>.lambda_function.lambda_handler.
Causa: paquete spa-cli instalado mal o falta el template empaquetado.
Solución: reinstalar (pip install --force-reinstall spa-cli).
Causa: spa_project.toml falta o es inválido.
Solución: ejecutar spa project init o crear el archivo manualmente.
Si handler.py ya existe o la sección TOML ya fue agregada, el comando no sobreescribe — emite [skip].
[+] Generado handler: src/authorizers/principal/handler.py
[+] Registrado [spa.api.lambda-authorizers.principal] en spa_project.toml
Listo. Asegúrate que el securityScheme 'principal_authorizer' esté declarado
en api.yaml para que el bridge lo enlace.
-
NAME corto, en
snake_case, alineado con el role del usuario que valida (ej.principal,admin,apikey,mobile). -
role_nameylambda_nameenkebab-case(convención AWS).
- El stub generado asume validación Cognito. Si el proyecto usa JWT propio o API key, reemplazar
validate_access_logincon el flujo correspondiente. - Mantener la firma
lambda_handler(event, context)y el retorno de policy IAM — esto garantiza que el mismo handler funcione en serverless y container sin ramas condicionales.
En el container, si necesitas saltarte la auth para debug:
AUTH_DISABLED=true uvicorn src.api_local.main_server:appEl middleware se vuelve passthrough.
Después de un Allow, el bridge inyecta:
request.state.authorizer = {
"principalId": "user",
"context": {"user": "<json string con atributos del usuario>"},
"scheme": "principal_authorizer"
}En modo container, si tu lambda_handler lee event.requestContext.authorizer.context.user, hay que propagarlo en el evento que el router le pasa. Esto no es automático hoy — ver issue abierto.