aws, iot, security, serverless

Serverloses Self-Service-IoT-Zertifikatsmanagement - Teil 1.

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

Diese Datei wurde automatisch von KI ubersetzt, es konnen Fehler auftreten

Ich habe im Laufe der Jahre mit mehreren verschiedenen IoT-Lösungen gearbeitet. Eine Sache, die ihnen allen gemeinsam ist, ist die Notwendigkeit von vertrauenswürdigen Zertifikaten, die sowohl zum Herstellen von Verbindungen als auch zur Identifizierung von Geräten verwendet werden können. Geräte und Server müssen sich gegenseitig vertrauen, um sichere Kommunikation herzustellen, die Datenintegrität sicherzustellen und böswillige Angriffe zu verhindern. Ein wichtiger Teil dieses Vertrauens ist die Public Key Infrastructure (PKI), bei der Zertifikate und Certificate Authorities (CAs) eine entscheidende Rolle spielen. Es besteht auch die Notwendigkeit, eine große Menge an Zertifikaten auf einfache Weise zu verwalten.

In einigen Projekten haben wir unsere eigene interne PKI-Lösung entwickelt, was mit Komplexität und Sicherheitsanforderungen einhergeht. In anderen haben wir SaaS-Lösungen verwendet, wie DigiCert IoT Trust Manager, AWS IoT Core und Amazon Private CA.

Wenn es jedoch um Entwicklung und manchmal sogar um Testumgebungen ging, war es nicht ungewöhnlich, dass wir selbstsignierte Zertifikate verwendet haben, mit einem einfachen Self-Service-Portal, um verschiedene Zertifikate zu erstellen.

In diesem Beitrag, der der erste von zwei Teilen ist, werden wir einige Grundlagen zu Zertifikaten und PKI einführen. Wir werden auch damit beginnen, die Grundlagen für eine serverlose API zu schaffen, die verwendet werden kann, um ein Self-Service-Portal für die Generierung von Zertifikaten einzurichten.

Warum eine Self-Service-API erstellen?

Wie bereits gesagt, müssen in einem IoT-System Tausende von Geräten mit Servern, IoT-Brokern und miteinander interagieren. Jede dieser Interaktionen muss sicher sein, was bedeutet:

  • Server müssen Geräte authentifizieren, um sicherzustellen, dass sie legitim sind.
  • Geräte müssen Server authentifizieren, um sicherzustellen, dass sie mit einer vertrauenswürdigen Quelle verbunden sind.

Zertifikate werden daher an Server und Geräte ausgegeben, wodurch eine Vertrauenskette entsteht, die bei einer Root-CA verankert ist.

Eine Self-Service-API für das Zertifikatsmanagement ermöglicht:

  • Automatisierung: Geräte und Server können Zertifikate programmgesteuert anfordern und erneuern.
  • Skalierbarkeit: Wenn unsere IoT-Umgebung wächst, kann die API den zunehmenden Bedarf an Zertifikaten bewältigen.
  • Lernen und Testen: Bevor man einen verwalteten Dienst einführt, hilft der Aufbau eines eigenen Zertifikatsystems dabei, zu verstehen, wie PKI funktioniert.

Überblick über Zertifikate, CAs und Vertrauen

Was ist eine Certificate Authority (CA)?

Eine CA ist eine vertrauenswürdige Entität, die für die Ausstellung digitaler Zertifikate verantwortlich ist. Diese Zertifikate binden einen öffentlichen Schlüssel an eine Identität (z. B. einen Server, ein Gerät oder einen Benutzer) und ermöglichen so Vertrauen zwischen Entitäten.

CAs sind in einer Hierarchie strukturiert:

Root-CA

  • Das ultimative Vertrauensanker.
  • Selbstsigniert und hochsicher.
  • Sollte niemals direkt End-Entity-Zertifikate ausstellen.

Zwischen-CA

  • Von der Root-CA ausgestellt und signiert.
  • Delegiert die Verantwortung für die Ausstellung von Zertifikaten an End-Entitäten (z. B. Geräte und Server).
  • Begrenzt die Exposition der Root-CA.

End-Entity-, Blatt-Zertifikate

  • Zertifikate für Server, IoT-Geräte oder Benutzer.
  • Von einer Zwischen-CA ausgestellt und für die Kommunikation zwischen Client und Server oder gegenseitige Authentifizierung verwendet.

Zertifikatsketten und Vertrauen

Eine Zertifikatskette ist eine Folge von Zertifikaten, bei der jedes Zertifikat in der Kette von dem nachfolgenden Zertifikat signiert wird. Sie stellt die hierarchische Beziehung zwischen einem Zertifikat und seinem Aussteller dar.

Bild einer Zertifikatskette

Eine Kette kann aus einem oder mehreren Zwischenzertifikaten bestehen. Im obigen Bild wird die Kette mit zwei Zwischen-CAs veranschaulicht.

Während eines TLS-Handshakes

Vereinfacht dargestellt würde der Handshake wie folgt ablaufen

  • Der Server präsentiert sein Zertifikat dem Client.
  • Der Client validiert das Serverzertifikat, indem er die Kette bis zu einer vertrauenswürdigen Root-CA in seinem Zertifikatspeicher zurückverfolgt. Bei Verwendung einer selbstsignierten Root-CA müssen Sie sicherstellen, dass das Bündel im Vertrauensspeicher vorhanden ist oder im Verbindungsversuch enthalten ist.

In einem Szenario, in dem gegenseitige Authentifizierung erforderlich ist, führt der Server den gleichen Prozess für das Client-Zertifikat durch.

Vertrauen über mehrere Zwischen-CAs hinweg

In einem IoT-Setup ist es üblich, dass eine Zwischen-CA Serverzertifikate ausstellt (z. B. für IoT-Broker) und eine andere Zwischen-CA Client-Zertifikate ausstellt (z. B. für Geräte). Damit ein Client-Zertifikat (signiert von einer Zwischen-CA) einem Server-Zertifikat (signiert von einer anderen Zwischen-CA) vertraut, müssen beide:

Teil derselben Vertrauenshierarchie sein.

  • Beide Zwischen-CAs müssen von derselben Root-CA signiert sein.
  • Die Root-CA ist der gemeinsame Vertrauensanker.

Gegen die Kette validiert werden.

  • Der Client überprüft die Zertifikatskette des Servers und verfolgt sie bis zur Root-CA zurück.
  • Der Server überprüft die Zertifikatskette des Clients auf ähnliche Weise.

Dieses Setup gewährleistet Skalierbarkeit und Trennung der Verantwortlichkeiten. Die Server-Zwischen-CA konzentriert sich auf Server und Broker. Die Geräte-Zwischen-CA konzentriert sich auf IoT-Geräte.

Implementierung

Nun lassen Sie uns mit der Implementierung dieser Self-Service-API beginnen. Wir werden sie vollständig serverlos mit Amazon API Gateway und Lambda-Funktionen bauen, wobei die Zertifikate in S3 und Certificate Manager gespeichert werden. Dieser erste Teil der Blogserie wird nur den ersten sehr grundlegenden Teil der API erstellen, den wir im zweiten Teil erweitern werden. Sie finden den gesamten Quellcode auf Serverless-Handbuch Self-Service-IoT-Zertifikatsmanagement

Bild einer API-Übersicht

Wir werden ein API Gateway mit drei Endpunkten einrichten, um unsere Root-CA, Zwischen-CA und Serverzertifikate zu erstellen. Alles wird in einem S3-Bucket gespeichert, und das Serverzertifikat wird auch in Certificate Manager importiert.

REST-API

Zunächst werfen wir einen Blick auf die REST-API und die Struktur.

EndpunktMethodeBeschreibung
/certificates/rootPOSTErstellen Sie eine neue Root-CA.
/certificates/intermediatePOSTErstellen Sie eine neue Zwischen-CA.
/certificates/serverPOSTErstellen Sie ein neues Serverzertifikat.

Ich habe mich entschieden, separate Pfade zu verwenden (z. B. /certificates/root, /certificates/intermediate, /certificates/server) anstatt einen einzigen Endpunkt mit einem Typ-Parameter (z. B. /certificates mit Typ als Eingabe), da ich der Meinung bin, dass dies besser mit REST-Prinzipien übereinstimmt und die Lesbarkeit und Benutzerfreundlichkeit der API verbessert.

Es ist einfacher, die API zu erweitern, mit einem neuen Zertifikatstyp (z. B. Gerät), da wir einen neuen Pfad wie /certificates/device erstellen können, ohne bestehende Pfade zu beeinflussen. Separate Pfade segmentieren Ressourcen auf natürliche Weise und reduzieren den Bedarf an Filterung auf der Client-Seite. Ich finde, dass das Abrufen aller Root-CAs mit GET /certificates/root einfacher ist als GET /certificates?type=root.

Gemeinsame Infrastruktur

Zunächst einmal lassen Sie uns die gemeinsame Infrastruktur bereitstellen, in diesem Fall ist es nur der S3-Bucket, wir werden später weitere Dinge in dieser Vorlage hinzufügen.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Zertifikat Self Service Gemeinsame Infrastruktur
Parameters:
  ApplicationName:
    Type: String
    Description: Name der übergeordneten Anwendung
    Default: image-moderation

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

Outputs:
  StorageBucketName:
    Description: Der Name des Zertifikatsbuckets
    Value: !Ref StorageBucket
    Export:
      Name: !Sub ${AWS::StackName}:certificate-bucket-name

Als nächstes können wir die Endpunkte mit SAM und AWS::Serverless::Api einrichten und die drei Lambda-Funktionen, die die API unterstützen.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: Erstellen Sie die API für das Self-Service-Zertifikatsmanagement
Parameters:
  ApplicationName:
    Type: String
    Description: Name der übergeordneten Anwendung
  CommonInfraStackName:
    Type: String
    Description: Der Name des gemeinsamen Stacks, der den EventBridge-Bus und mehr enthält

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 zum Erstellen und Verwalten von Zertifikaten
      Name: !Sub ${ApplicationName}-api
      StageName: prod
      OpenApiVersion: '3.0.1'
      AlwaysDeploy: true
      EndpointConfiguration: REGIONAL

Unsere Lambda-Funktionen sind in Python und wir verwenden die Kryptografie-Bibliothek, um die Zertifikate zu erstellen, unten ist die Implementierung für die Erstellung eines Serverzertifikats. Für eine vollständige Implementierung besuchen Sie Serverless-Handbuch Self-Service-IoT-Zertifikatsmanagement.

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,
):
    # Laden Sie den privaten Schlüssel und das Zertifikat der Zwischen-CA
    intermediate_private_key = serialization.load_pem_private_key(
        intermediate_private_key_pem, password=None
    )
    intermediate_cert = x509.load_pem_x509_certificate(intermediate_cert_pem)

    # Generieren Sie einen privaten Schlüssel für das Serverzertifikat
    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)  # Signiert von der Zwischen-CA
        .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")

    # Zwischen-CA-Privatschlüssel und -Zertifikat aus S3 abrufen
    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()

    # Die Zertifikatskette aus S3 abrufen
    cert_chain_pem = s3_client.get_object(
        Bucket=bucket_name, Key="intermediate_ca/certificate_chain.pem"
    )["Body"].read()

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

    # Serverzertifikat und Privatschlüssel in S3 hochladen
    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
    )

    # Zertifikat in ACM importieren
    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": "Serverzertifikat erstellt, in S3 hochgeladen und in ACM importiert.",
                "fqdn": body["fqdn"],
                "s3_folder": s3_folder,
                "acm_certificate_arn": response["CertificateArn"],
            }
        ),
    }

Fazit

Dieser Beitrag deckte alles von Zertifikatsketten bis zur Validierung von Vertrauen in IoT-Systemen ab. Das Erstellen einer eigenen Self-Service-API ist eine gute Möglichkeit, Zertifikate und PKI kennenzulernen. In der Produktion sollten Sie jedoch immer verwaltete Lösungen wählen, die Automatisierung, Compliance und Skalierbarkeit bieten.

Zertifikate sind das Fundament der IoT-Sicherheit
Zertifikate und CAs gewährleisten sichere, authentifizierte Kommunikation in IoT-Ökosystemen.

Die Rolle von Zwischen-CAs
Die Delegation der Zertifikatsausstellung an Zwischen-CAs verbessert die Skalierbarkeit und begrenzt die Exposition.

Vertrauen wird auf Hierarchien aufgebaut
Das Vertrauen zwischen Geräten und Servern beruht auf gemeinsamen Root-CAs und validierten Zertifikatsketten.

Bauen Sie zum Lernen, verwenden Sie verwaltete Dienste für die Produktion Während diese API ein großartiges Lernwerkzeug ist, eignen sich Dienste wie AWS Private CA oder Let’s Encrypt besser für die Produktion.

Um den vollständigen Quellcode zu erhalten und ihn selbst zu deployen, besuchen Sie Serverless-Handbuch Self-Service-IoT-Zertifikatsmanagement

Abschließende Worte

Vergessen Sie nicht, mir auf LinkedIn und X zu folgen, um weitere Inhalte zu erhalten, und lesen Sie den Rest meiner Blogs

Wie Werner sagt! Jetzt loslegen!