aws, iot, security, serverless

Gerenciamento de certificados IoT autosserviço sem servidor - Parte 1.

2024-11-27
This post cover image
aws cloud serverless security iot

Este arquivo foi traduzido automaticamente por IA, erros podem ocorrer

Estive trabalhando com várias soluções diferentes de IoT ao longo dos anos. Uma coisa que todas elas têm em comum é a necessidade de certificados confiáveis que possam ser usados tanto para estabelecer conexões quanto para identidades de dispositivos. Dispositivos e servidores precisam confiar uns nos outros para estabelecer comunicação segura, garantir a integridade dos dados e prevenir ataques maliciosos. Uma parte importante dessa confiança é a Infraestrutura de Chave Pública (PKI), onde certificados e Autoridades de Certificação (ACs) desempenham papéis vitais, além disso, há a necessidade de gerenciar uma grande quantidade de certificados de maneira fácil.

Em alguns projetos, temos implementado nossa própria solução interna de PKI, o que vem com complexidade e requisitos de segurança. Em outros, temos usado soluções SaaS, como DigiCert IoT Trust Manager, AWS IoT Core e Amazon Private CA.

No entanto, quando se tratava de desenvolvimento, e às vezes até ambientes de teste, não era incomum usarmos algum tipo de certificado autoassinado, com um portal autosserviço fácil para criar diferentes certificados.

Neste post, que é a primeira parte de duas, apresentaremos alguns conceitos básicos sobre certificados e PKI. Também começaremos a criar as fundações para uma API sem servidor que pode ser usada para criar um portal autosserviço para geração de certificados.

Por que construir uma API autosserviço?

Como dito, em um sistema IoT, milhares de dispositivos precisam interagir com servidores, brokers IoT e entre si. Cada uma dessas interações deve ser segura, o que significa:

  • Os servidores devem autenticar dispositivos para garantir que são legítimos.
  • Os dispositivos devem autenticar servidores para garantir que estão se conectando a uma fonte confiável.

Portanto, certificados são emitidos para servidores e dispositivos, criando uma cadeia de confiança ancorada em uma AC Raiz.

Uma API autosserviço para gerenciamento de certificados permite:

  • Automação: Dispositivos e servidores podem solicitar e renovar certificados programaticamente.
  • Escalabilidade: À medida que nosso ambiente IoT cresce, a API pode lidar com a crescente demanda por certificados.
  • Aprendizado e Teste: Antes de adotar um serviço gerenciado, construir seu próprio sistema de certificados ajuda a entender como a PKI funciona.

Visão geral de Certificados, ACs e Confiança

O que é uma Autoridade de Certificação (AC)?

Uma AC é uma entidade confiável responsável por emitir certificados digitais. Esses certificados vinculam uma chave pública a uma identidade (por exemplo, um servidor, dispositivo ou usuário), permitindo confiança entre entidades.

As ACs são estruturadas em uma hierarquia:

AC Raiz

  • A âncora de confiança definitiva.
  • Autoassinada e altamente segura.
  • Nunca deve ser usada para emitir certificados de entidade final diretamente.

AC Intermediária

  • Emitida e assinada pela AC Raiz.
  • Delega a responsabilidade de emitir certificados para entidades finais (por exemplo, dispositivos e servidores).
  • Limita a exposição da AC Raiz.

Certificados de Entidade Final, folha

  • Certificados para servidores, dispositivos IoT ou usuários.
  • Emitidos por uma AC Intermediária e usados na comunicação cliente-servidor ou autenticação mútua.

Cadeias de Certificados e Confiança

Uma cadeia de certificados é uma sequência de certificados, onde cada certificado na cadeia é assinado pelo certificado subsequente. Ela representa a relação hierárquica entre um certificado e seu emissor.

Imagem mostrando uma cadeia de certificados

Uma cadeia pode consistir em um ou vários certificados intermediários. Na imagem acima, a cadeia é ilustrada com duas ACs Intermediárias.

Durante um handshake TLS

Simplificando muito, o handshake seria

  • O servidor apresenta seu certificado ao cliente.
  • O cliente valida o certificado do servidor rastreando a cadeia de volta para uma AC Raiz confiável em seu armazenamento de certificados. Ao usar uma AC Raiz autoassinada, você precisa garantir que o pacote esteja presente no armazenamento de confiança ou incluído na tentativa de conexão.

Em um cenário onde a autenticação mútua é necessária, o servidor executa o mesmo processo para o certificado do cliente.

Confiança em várias ACs Intermediárias

Em um ambiente IoT, é comum ter uma AC Intermediária emitindo certificados de servidor (por exemplo, para brokers IoT) e outra AC Intermediária emitindo certificados de cliente (por exemplo, para dispositivos). Para que um certificado de cliente (assinado por uma AC Intermediária) confie em um certificado de servidor (assinado por uma AC Intermediária diferente), ambos devem:

Fazer parte da mesma hierarquia de confiança.

  • Ambas as ACs Intermediárias devem ser assinadas pela mesma AC Raiz.
  • A AC Raiz é a âncora de confiança comum.

Ser validados contra a cadeia.

  • O cliente verifica a cadeia de certificados do servidor, rastreando-a de volta para a AC Raiz.
  • O servidor verifica a cadeia de certificados do cliente da mesma forma.

Essa configuração garante escalabilidade e separação de responsabilidades. A AC Intermediária de Servidor se concentra em servidores e brokers. A AC Intermediária de Dispositivo se concentra em dispositivos IoT.

Implementação

Agora vamos começar a implementar esta API autosserviço. Vamos construir isso completamente sem servidor usando Amazon API Gateway e funções Lambda, com armazenamento dos certificados em S3 e Certificate Manager. Esta primeira parte da série de blogs irá criar apenas a primeira parte muito básica da API, que iremos estender na segunda parte. Você pode encontrar todo o código-fonte em Serverless-Handbook Gerenciamento de Certificados IoT Autosserviço

Imagem mostrando uma visão geral da API

Vamos configurar um API Gateway com três endpoints para criar nossa AC Raiz, AC Intermediária e certificado de Servidor. Tudo será armazenado em um bucket S3, e o certificado de servidor também será importado para o Certificate Manager.

API REST

Primeiro, vejamos a API REST e a estrutura.

EndpointMétodoDescrição
/certificates/rootPOSTCriar uma nova AC Raiz.
/certificates/intermediatePOSTCriar uma nova AC Intermediária.
/certificates/serverPOSTCriar um novo certificado de servidor.

Decidi usar caminhos separados (por exemplo, /certificates/root, /certificates/intermediate, /certificates/server) em vez de um único endpoint com um parâmetro de tipo (por exemplo, /certificates com tipo como entrada), pois acho que isso se alinha melhor aos princípios REST e melhora a legibilidade e usabilidade da API.

É mais fácil estender a API, com um novo tipo de certificado (por exemplo, dispositivo), pois podemos criar um novo caminho como /certificates/device sem impactar os caminhos existentes. Caminhos separados segmentam naturalmente os recursos, reduzindo a necessidade de filtragem no lado do cliente. Acho que recuperar todas as ACs Raiz é mais simples com GET /certificates/root do que GET /certificates?type=root.

Infraestrutura comum

Primeiro de tudo, vamos implantar a infraestrutura comum, neste caso, é apenas o bucket S3, adicionaremos mais coisas neste modelo mais tarde.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Infraestrutura Comum de Gerenciamento de Certificados Autosserviço
Parameters:
  ApplicationName:
    Type: String
    Description: Nome do aplicativo proprietário
    Default: image-moderation

Resources:
  StorageBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub ${ApplicationName}-storage-bucket

Outputs:
  StorageBucketName:
    Description: O nome do bucket de certificados
    Value: !Ref StorageBucket
    Export:
      Name: !Sub ${AWS::StackName}:certificate-bucket-name

Em seguida, podemos configurar os endpoints usando SAM e AWS::Serverless::Api e as três funções Lambda que suportam a API.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Criar a API para gerenciamento autosserviço de certificados
Parameters:
  ApplicationName:
    Type: String
    Description: Nome do aplicativo proprietário
  CommonInfraStackName:
    Type: String
    Description: O nome da pilha comum que contém o EventBridge Bus e mais

Globals:
  Function:
    Timeout: 30
    MemorySize: 2048
    Runtime: python3.12
    Environment:
        Variables:
          CERTIFICATE_BUCKET_NAME: 
            Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"

Resources:
  LambdaGenerateRootCA:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/API/GenerateRootCA
      Handler: handler.handler
      Policies:
        - S3FullAccessPolicy:
            BucketName: 
              Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
      Events:
        CreateRootCAApi:
          Type: Api
          Properties:
            Path: /certificates/root
            Method: post
            RestApiId: !Ref GenerateCertificatesApi

  LambdaGenerateIntermediateCA:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/API/GenerateIntermediateCA
      Handler: handler.handler
      Policies:
        - S3FullAccessPolicy:
            BucketName: 
              Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
      Events:
        CreateIntermediateCAApi:
          Type: Api
          Properties:
            Path: /certificates/intermediate
            Method: post
            RestApiId: !Ref GenerateCertificatesApi   

  LambdaGenerateDeviceCertificate:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/API/GenerateDeviceCert
      Handler: handler.handler
      Policies:
        - S3FullAccessPolicy:
            BucketName: 
              Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"      

  LambdaGenerateServerCertificate:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: Lambda/API/GenerateServerCert
      Handler: handler.handler
      Policies:
        - S3FullAccessPolicy:
            BucketName: 
              Fn::ImportValue: !Sub "${CommonInfraStackName}:certificate-bucket-name"
        - Version: "2012-10-17"
          Statement:
            Action:
              - acm:*
            Effect: Allow
            Resource: "*"
      Events:
        CreateServerCAApi:
          Type: Api
          Properties:
            Path: /certificates/server
            Method: post
            RestApiId: !Ref GenerateCertificatesApi

  GenerateCertificatesApi:
    Type: AWS::Serverless::Api
    Properties:
      Description: API para criar e gerenciar certificados
      Name: !Sub ${ApplicationName}-api
      StageName: prod
      OpenApiVersion: '3.0.1'
      AlwaysDeploy: true
      EndpointConfiguration: REGIONAL

Nossas funções Lambda estão em Python e usamos a biblioteca cryptography para criar os certificados, abaixo está a implementação para criar um certificado de servidor. Para uma implementação completa, visite Serverless-Handbook Gerenciamento de Certificados IoT Autosserviço.

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,
):
    # Carregar a chave privada e o certificado da AC Intermediária
    intermediate_private_key = serialization.load_pem_private_key(
        intermediate_private_key_pem, password=None
    )
    intermediate_cert = x509.load_pem_x509_certificate(intermediate_cert_pem)

    # Gerar uma chave privada para o certificado de 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)  # Assinado pela AC Intermediária
        .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")

    # Buscar a chave privada e o certificado da AC Intermediária do 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()

    # Buscar a cadeia de certificados do S3
    cert_chain_pem = s3_client.get_object(
        Bucket=bucket_name, Key="intermediate_ca/certificate_chain.pem"
    )["Body"].read()

    # Criar 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"]}/"

    # Upload do Certificado de Servidor e Chave Privada para 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 o certificado para o 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 criado, enviado para S3 e importado para o ACM.",
                "fqdn": body["fqdn"],
                "s3_folder": s3_folder,
                "acm_certificate_arn": response["CertificateArn"],
            }
        ),
    }

Conclusão

Este blog cobriu tudo, desde cadeias de certificados até validar confiança em sistemas IoT. Construir sua própria API autosserviço é uma boa maneira de aprender sobre certificados e PKI. Em produção, no entanto, sempre opte por soluções gerenciadas que oferecem automação, conformidade e escalabilidade.

Certificados São a Base da Segurança IoT
Certificados e ACs garantem comunicação segura e autenticada nos ecossistemas IoT.

O Papel das ACs Intermediárias
Delegar a emissão de certificados para ACs Intermediárias melhora a escalabilidade e limita a exposição.

A Confiança é Construída em Hierarquias
A confiança entre dispositivos e servidores depende de ACs Raiz compartilhadas e cadeias de certificados validadas.

Construa para Aprender, Use Serviços Gerenciados para Produção
Enquanto esta API é uma ótima ferramenta de aprendizado, serviços como AWS Private CA ou Let’s Encrypt são mais adequados para produção.

Para obter o código-fonte completo e implantá-lo você mesmo, visite Serverless-Handbook Gerenciamento de Certificados IoT Autosserviço

Palavras Finais

Não se esqueça de me seguir no LinkedIn e X para mais conteúdo, e leia o restante dos meus Blogs

Como Werner diz! Agora Vá Construir!