Gestión de certificados IoT autónomo sin servidor - Parte 1.

Este archivo ha sido traducido automaticamente por IA, pueden ocurrir errores
He estado trabajando con varias soluciones IoT diferentes a lo largo de los años. Una cosa que todas tienen en común es la necesidad de certificados confiables que se puedan usar tanto para establecer conexiones como para identificar dispositivos. Los dispositivos y los servidores deben confiar el uno en el otro para establecer una comunicación segura, garantizar la integridad de los datos y prevenir ataques maliciosos. Una parte importante de esta confianza es la Infraestructura de Clave Pública (PKI), donde los certificados y las Autoridades de Certificación (CA) juegan roles vitales, también existe la necesidad de administrar una gran cantidad de certificados de manera sencilla.
En algunos proyectos, hemos estado implementando nuestra propia solución interna de PKI, lo cual conlleva complejidad y requisitos de seguridad. En otros, hemos estado utilizando soluciones SaaS, como DigiCert IoT Trust Manager, AWS IoT Core y Amazon Private CA.
Sin embargo, cuando se trataba de desarrollo, y a veces incluso de entornos de prueba, no era raro que usáramos algún tipo de certificados autofirmados, con un portal de autoservicio sencillo para crear diferentes certificados.
En esta publicación, que es la primera parte de dos, introduciremos algunos conceptos básicos sobre certificados y PKI. También comenzaremos a crear los fundamentos de una API sin servidor que se pueda usar para crear un portal de autoservicio para la generación de certificados.
ADVERTENCIA
La solución que construyo en esta serie de publicaciones NO es adecuada para una configuración de producción. ¡Esto está puramente destinado a entornos de desarrollo y con fines de aprendizaje!
En un entorno de producción necesitamos:
Para estas necesidades, los servicios administrados como AWS Private CA, DigiCert IoT Trust Manager y Let’s Encrypt son ideales.
¡En esta primera versión de la API no usamos ningún tipo de Autorización! ¡Esto se agregará en la siguiente parte!
¿Por qué construir una API de autoservicio?
Como se dijo, en un sistema IoT, miles de dispositivos deben interactuar con servidores, corredores IoT y entre sí. Cada una de estas interacciones debe ser segura, lo que significa:
- Los servidores deben autenticar los dispositivos para garantizar que sean legítimos.
- Los dispositivos deben autenticar los servidores para garantizar que se están conectando a una fuente confiable.
Por lo tanto, se emiten certificados a los servidores y dispositivos, creando una cadena de confianza anclada en una CA raíz.
Una API de autoservicio para la gestión de certificados permite:
- Automatización: Los dispositivos y servidores pueden solicitar y renovar certificados de manera programática.
- Escalabilidad: A medida que nuestro entorno IoT crece, la API puede manejar la creciente demanda de certificados.
- Aprendizaje y pruebas: Antes de adoptar un servicio administrado, construir su propio sistema de certificados le ayuda a comprender cómo funciona PKI.
Visión general de los certificados, las CA y la confianza
¿Qué es una Autoridad de Certificación (CA)?
Una CA es una entidad confiable responsable de emitir certificados digitales. Estos certificados vinculan una clave pública a una identidad (por ejemplo, un servidor, dispositivo o usuario), lo que permite la confianza entre entidades.
Las CA están estructuradas en una jerarquía:
CA raíz
- El anclaje de confianza definitivo.
- Autofirmado y altamente seguro.
- Nunca debe usarse para emitir certificados de entidad final directamente.
CA intermedia
- Emitido y firmado por la CA raíz.
- Delega la responsabilidad de emitir certificados a entidades finales (por ejemplo, dispositivos y servidores).
- Limita la exposición de la CA raíz.
Certificados de entidad final, hoja
- Certificados para servidores, dispositivos IoT o usuarios.
- Emitidos por una CA intermedia y utilizados en la comunicación cliente-servidor o autenticación mutua.
Cadenas de certificados y confianza
Una cadena de certificados es una secuencia de certificados, donde cada certificado en la cadena está firmado por el siguiente certificado. Representa la relación jerárquica entre un certificado y su emisor.

Una cadena puede consistir en uno o varios certificados intermedios. En la imagen de arriba, la cadena se ilustra con dos CA intermedias.
Durante un intercambio TLS
Simplificando mucho, el intercambio sería
- El servidor presenta su certificado al cliente.
- El cliente valida el certificado del servidor rastreando la cadena hasta una CA raíz confiable en su almacén de certificados. Cuando se usa una CA raíz autofirmada, debe asegurarse de que el paquete esté presente en el almacén de confianza o incluido en la solicitud de conexión.
En un escenario en el que se requiere autenticación mutua, el servidor realiza el mismo proceso para el certificado del cliente.
Confianza a través de varias CA intermedias
En una configuración IoT, es común tener una CA intermedia que emite certificados de servidor (por ejemplo, para corredores IoT) y otra CA intermedia que emite certificados de cliente (por ejemplo, para dispositivos). Para que un certificado de cliente (firmado por una CA intermedia) confíe en un certificado de servidor (firmado por una CA intermedia diferente), ambos deben:
Ser parte de la misma jerarquía de confianza.
- Ambas CA intermedias deben estar firmadas por la misma CA raíz.
- La CA raíz es el anclaje de confianza común.
Ser validados contra la cadena.
- El cliente verifica la cadena de certificados del servidor, rastreándola hasta la CA raíz.
- El servidor verifica la cadena de certificados del cliente de manera similar.
Esta configuración garantiza escalabilidad y separación de responsabilidades. La CA intermedia del servidor se enfoca en servidores y corredores. La CA intermedia del dispositivo se enfoca en dispositivos IoT.
Implementación
Ahora comencemos a implementar esta API de autoservicio. La construiremos completamente sin servidor usando Amazon API Gateway y funciones Lambda, con el almacenamiento de los certificados en S3 y Certificate Manager. Esta primera parte de la serie de publicaciones solo creará la primera parte muy básica de la API, que extendemos en la segunda parte. Puede encontrar todo el código fuente en Gestión de certificados IoT autónomo sin servidor

Configuraremos un API Gateway con tres puntos finales para crear nuestra CA raíz, CA intermedia y certificado de servidor. Todo se almacenará en un bucket S3, y el certificado del servidor también se importará a Certificate Manager.
API REST
Primero, veamos la API REST y la estructura.
| Punto final | Método | Descripción |
|---|---|---|
/certificates/root | POST | Crear una nueva CA raíz. |
/certificates/intermediate | POST | Crear una nueva CA intermedia. |
/certificates/server | POST | Crear un nuevo certificado de servidor. |
Decidí usar rutas separadas (por ejemplo, /certificates/root, /certificates/intermediate, /certificates/server) en lugar de un solo punto final con un parámetro de tipo (por ejemplo, /certificates con tipo como entrada) ya que siento que esto se alinea mejor con los principios REST y mejora la legibilidad y usabilidad de la API.
Es más fácil extender la API, con un nuevo tipo de certificado (por ejemplo, dispositivo), ya que podemos crear una nueva ruta como /certificates/device sin afectar las rutas existentes. Las rutas separadas segmentan naturalmente los recursos, reduciendo la necesidad de filtrado en el lado del cliente. Siento que recuperar todas las CA raíz es más simple con GET /certificates/root que GET /certificates?type=root.
Infraestructura común
Primero de todo, despleguemos la infraestructura común, en este caso solo es el bucket S3, más adelante agregaremos más cosas a esta plantilla.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Infraestructura común de autoservicio de certificados
Parámetros:
ApplicationName:
Tipo: Cadena
Descripción: Nombre de la aplicación propietaria
Predeterminado: moderación-de-imágenes
Recursos:
StorageBucket:
Tipo: AWS::S3::Bucket
DeletionPolicy: Retain
UpdateReplacePolicy: Retain
Propiedades:
BucketName: !Sub ${ApplicationName}-storage-bucket
Salidas:
StorageBucketName:
Descripción: El nombre del bucket de certificados
Valor: !Ref StorageBucket
Exportar:
Nombre: !Sub ${AWS::StackName}:certificate-bucket-nameA continuación, podemos configurar los puntos finales usando SAM y AWS::Serverless::Api y las tres funciones Lambda que respaldan la API.
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Descripción: Crear la API para la gestión de certificados de autoservicio
Parámetros:
ApplicationName:
Tipo: Cadena
Descripción: Nombre de la aplicación propietaria
CommonInfraStackName:
Tipo: Cadena
Descripción: El nombre de la pila común que contiene el EventBridge Bus y más
Globals:
Función:
Tiempo de espera: 30
Tamaño de memoria: 2048
Tiempo de ejecución: python3.12
Entorno:
Variables:
CERTIFICATE_BUCKET_NAME:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Recursos:
LambdaGenerateRootCA:
Tipo: AWS::Serverless::Function
Propiedades:
CodeUri: Lambda/API/GenerateRootCA
Handler: handler.handler
Políticas:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Eventos:
CreateRootCAApi:
Tipo: Api
Propiedades:
Ruta: /certificados/root
Método: post
RestApiId: !Ref GenerateCertificatesApi
LambdaGenerateIntermediateCA:
Tipo: AWS::Serverless::Function
Propiedades:
CodeUri: Lambda/API/GenerateIntermediateCA
Handler: handler.handler
Políticas:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
Eventos:
CreateIntermediateCAApi:
Tipo: Api
Propiedades:
Ruta: /certificados/intermedio
Método: post
RestApiId: !Ref GenerateCertificatesApi
LambdaGenerateDeviceCertificate:
Tipo: AWS::Serverless::Function
Propiedades:
CodeUri: Lambda/API/GenerateDeviceCert
Handler: handler.handler
Políticas:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
LambdaGenerateServerCertificate:
Tipo: AWS::Serverless::Function
Propiedades:
CodeUri: Lambda/API/GenerateServerCert
Handler: handler.handler
Políticas:
- S3FullAccessPolicy:
BucketName:
Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
- Versión: "2012-10-17"
Declaración:
Acción:
- acm:*
Efecto: Permitir
Recurso: "*"
Eventos:
CreateServerCAApi:
Tipo: Api
Propiedades:
Ruta: /certificados/servidor
Método: post
RestApiId: !Ref GenerateCertificatesApi
GenerateCertificatesApi:
Tipo: AWS::Serverless::Api
Propiedades:
Descripción: API para crear y administrar certificados
Nombre: !Sub ${ApplicationName}-api
StageName: prod
OpenApiVersion: '3.0.1'
AlwaysDeploy: verdadero
EndpointConfiguration: REGIONALNuestras funciones Lambda están en Python y usamos la biblioteca cryptography para crear los certificados, a continuación está la implementación para crear un certificado de servidor. Para una implementación completa, visite Gestión de certificados IoT autónomo sin servidor.
import boto3
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization, hashes
import datetime
import os
import json
s3_client = boto3.client("s3")
acm_client = boto3.client("acm")
def create_server_certificate(
intermediate_private_key_pem,
intermediate_cert_pem,
fqdn,
country,
state,
organization,
validity_days,
):
# Cargar la clave privada y el certificado de la CA intermedia
intermediate_private_key = serialization.load_pem_private_key(
intermediate_private_key_pem, password=None
)
intermediate_cert = x509.load_pem_x509_certificate(intermediate_cert_pem)
# Generar una clave privada para el certificado del servidor
server_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
server_private_key_pem = server_private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
subject = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
x509.NameAttribute(NameOID.COMMON_NAME, fqdn),
]
)
server_certificate = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(intermediate_cert.subject) # Firmado por CA intermedia
.public_key(server_private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.datetime.now(datetime.timezone.utc))
.not_valid_after(
datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=validity_days)
)
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(fqdn)]),
critical=False,
)
.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
)
.add_extension(
x509.KeyUsage(
digital_signature=True,
key_encipherment=True,
key_cert_sign=False,
crl_sign=False,
content_commitment=False,
data_encipherment=False,
encipher_only=False,
decipher_only=False,
key_agreement=False,
),
critical=True,
)
.sign(intermediate_private_key, hashes.SHA256())
)
server_cert_pem = server_certificate.public_bytes(serialization.Encoding.PEM)
return server_private_key_pem, server_cert_pem
def handler(event, context):
body = json.loads(event["body"])
bucket_name = os.environ.get("CERTIFICATE_BUCKET_NAME")
# Obtener la clave privada y el certificado de la CA intermedia de S3
intermediate_private_key = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/private_key.pem"
)["Body"].read()
intermediate_cert = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/certificate.pem"
)["Body"].read()
# Obtener la cadena de certificados de S3
cert_chain_pem = s3_client.get_object(
Bucket=bucket_name, Key="intermediate_ca/certificate_chain.pem"
)["Body"].read()
# Crear certificado de servidor
server_private_key_pem, server_cert_pem = create_server_certificate(
intermediate_private_key,
intermediate_cert,
body["fqdn"],
body["country"],
body["state"],
body["organization"],
body["validity_days"],
)
s3_folder = f"server_certificates/{body["fqdn"]}/"
# Cargar el certificado del servidor y la clave privada en S3
s3_client.put_object(
Bucket=bucket_name,
Key=f"{s3_folder}private_key.pem",
Body=server_private_key_pem,
)
s3_client.put_object(
Bucket=bucket_name, Key=f"{s3_folder}certificate.pem", Body=server_cert_pem
)
# Importar el certificado a ACM
response = acm_client.import_certificate(
Certificate=server_cert_pem,
PrivateKey=server_private_key_pem,
CertificateChain=cert_chain_pem,
)
return {
"statusCode": 200,
"body": json.dumps(
{
"message": "Certificado de servidor creado, cargado en S3 e importado a ACM.",
"fqdn": body["fqdn"],
"s3_folder": s3_folder,
"acm_certificate_arn": response["CertificateArn"],
}
),
}Conclusión
Esta publicación cubrió todo, desde cadenas de certificados hasta validar la confianza en los sistemas IoT. Construir su propia API de autoservicio es una buena forma de aprender sobre certificados y PKI. Sin embargo, en producción, siempre opte por soluciones administradas que ofrezcan automatización, cumplimiento y escalabilidad.
Los certificados son la base de la seguridad IoT
Los certificados y las CA garantizan una comunicación segura y autenticada en los ecosistemas IoT.
El papel de las CA intermedias
Delegar la emisión de certificados a las CA intermedias mejora la escalabilidad y limita la exposición.
La confianza se construye sobre jerarquías
La confianza entre dispositivos y servidores se basa en CA raíz compartidas y cadenas de certificados validadas.
Construya para aprender, use servicios administrados para producción
Si bien esta API es una excelente herramienta de aprendizaje, servicios como AWS Private CA o Let’s Encrypt son más adecuados para producción.
Para obtener el código fuente completo y desplegarlo usted mismo, visite Gestión de certificados IoT autónomo sin servidor
Palabras finales
No olvide seguirme en LinkedIn y X para más contenido, y lea el resto de mis Publicaciones
Como dice Werner ¡Ahora vaya a construir!