DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Microservices Zone»Mutual TLS avec gRPC entre les services Python et Go
    Microservices Zone

    Mutual TLS avec gRPC entre les services Python et Go

    novembre 18, 2021
    Mutual TLS avec gRPC entre les services Python et Go
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Ce didacticiel vous guide tout au long du processus de connexion de services écrits en Python et Go via le framework gRPC à l’aide de l’authentification TLS mutuelle. Je suppose que le lecteur est quelque peu familier avec le développement Python/Django et Go et omet donc la plupart des choses ennuyeuses comme l’amorçage de virtualenv avec l’application Django ou comment le « gérer.py runserver ».
    Le code final peut être trouvé ici.

    introduction

    J’ai un ancien système en Python qui subit une refonte importante. C’est un système à deux composants :

    • Webapp est une application Web orientée utilisateur construite avec le framework Django. Il agit comme une API client et se connecte à plusieurs nœuds pour effectuer certaines actions.
    • Chaque nœud (serveur) est un serveur simple écrit en Python qui réside derrière Nginx. Certains nœuds vivent en dehors d’un réseau privé et la communication se fait sur un réseau public.

    Après un certain travail de nettoyage, de refactorisation et de test, le client satisfait à peu près ses exigences. D’un autre côté, le serveur pose problème – il est mal écrit, a des problèmes de stabilité et les performances deviennent un problème sérieux. La solution évidente ici est de réécrire le serveur en Go. Go (Golang) n’a pas besoin d’une introduction et aiderait à la performance.

    Le seul problème ici est une barrière de communication entre Python et Go.

    L’API JSON existante utilisée pour la communication entre le client et le serveur est ancienne et non documentée. Exactement le cas lorsqu’il est plus facile de le reconstruire à partir de zéro au lieu d’essayer de le faire revivre. Réécrire cette API en REST/JSON est relativement facile, mais JSON en tant que format d’échange ne fournira pas l’interchangeabilité et la compatibilité des types entre Python et Go. Les systèmes de types sont différents dans ces deux langues, et il serait fastidieux et sujet aux erreurs de les faire fonctionner.

    Une meilleure solution ici consiste à utiliser un format de sérialisation multiplateforme comme les tampons de protocole (protobuf). Il est conçu pour fournir une compatibilité multiplateforme et est bien pris en charge dans Python et Go, et il est également plus petit et plus rapide que JSON. Protobuf peut être utilisé avec l’API REST pour assurer l’interopérabilité des données entre les langages de programmation. Mais une solution encore meilleure consiste à utiliser le framework gRPC pour remplacer entièrement l’ancienne API.

    Le gRPC est un cadre d’appel de procédure à distance (RPC) qui fonctionne très bien dans les scénarios de communication interservices. Il utilise des tampons de protocole à la fois comme langage de définition d’interface (IDL) et comme format d’échange de messages. gRPC utilise HTTP/2 comme transport et prend en charge le protocole Transport Layer Security (TLS), et il peut fonctionner sans TLS – en gros, c’est ce que la plupart des tutoriels nous montrent. De cette façon, la communication se fait via le protocole h2c, essentiellement en texte brut HTTP/2 sans cryptage TLS. Cependant, lorsque la communication est effectuée sur un réseau public, TLS est une exigence. Et compte tenu des menaces de sécurité modernes, TLS doit être envisagé même pour les connexions aux réseaux privés. La source

    La communication de service à service dans le cas de ce système ne nécessite pas de distinguer les clients ou de leur accorder différentes autorisations. Néanmoins, il est important de s’assurer que seuls les clients autorisés parlent aux serveurs. Et il est facile à mettre en œuvre en utilisant le TLS mutuel (mTLS) comme mécanisme d’authentification.

    Normalement, dans TLS, le serveur dispose d’un certificat et d’une paire de clés publique/privée, contrairement au client. Le serveur envoie ensuite son certificat à un client pour vérification. Dans mTLS, le serveur et le client ont des certificats, et le serveur vérifie également le certificat du client. Ce n’est qu’après que le serveur accorde l’accès à un client. La source

    Créons quelque chose de similaire – un simple service Web Python/Django qui appellera le serveur Go via gRPC/mTLS et affichera les résultats dans un navigateur, et commencera par la structure du référentiel.

    Disposition des codes

    L’utilisation d’un référentiel unique (monorepo) pour un tel projet supprime le besoin de partager le schéma d’API. Tout le monde a des préférences personnelles sur la façon d’organiser une base de code, gardez juste à l’esprit que Protobuf Compiler, Protoc a ses propres idées sur la façon dont le code doit être organisé. Le placement des fichiers proto influence le code compilé. Et cela peut nécessiter quelques expérimentations avec les indicateurs du compilateur pour générer du code fonctionnel. Placez les fichiers proto en dehors des dossiers principaux avec le code, afin que la réorganisation du code n’interrompe pas la compilation proto.

    Je suggère d’utiliser une structure de répertoires comme celle-ci :

    tree -L 1 -d .
    .
    ├── certs
    ├── client
    ├── proto
    └── server
    
    • Certs – notre infrastructure à clé publique pour les certificats auto-signés.
    • Client – Application Web Python/Django et également client gRPC pour l’API. C’est essentiellement le résultat d’un django-admin startproject client . avec une configuration allégée car aucune base de données n’est requise.
    • Proto – est l’endroit où placer les fichiers sources de protobuf pour gRPC.
    • Serveur – serveur gRPC dans Go.

    Infrastructure à clé publique

    Pour commencer avec TLS, vous avez besoin de certificats pour le client et le serveur. Pour créer des certificats auto-signés, je suggère la boîte à outils PKI de CloudFlare, CFSSL.

    Tout d’abord, vous devez créer une autorité de certification (CA) qui sera utilisée pour générer des certificats TLS pour le serveur et le client. Ce certificat CA est également utilisé pour vérifier l’authenticité des certificats d’autres parties lors de l’établissement d’une connexion TLS.

    CFSSL configuré via des fichiers JSON et fournit des commandes pour générer des modèles de configuration par défaut pour commencer :

    cd certs
    cfssl print-defaults config > ca-config.json
    

    Par défaut, ca-config.json fournit des profils suffisants pour nos besoins. Générons la configuration, le certificat et la clé privée de la demande de signature de certificat CA :

    cat > ca-csr.json <<EOF
    {
      "CN": "CA",
      "key": {
          "algo": "ecdsa",
          "size": 256
      },
      "names": [
          {
              "C": "US",
              "ST": "CA",
              "L": "San Francisco"
          }
      ]
    }
    EOF
    
    cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
    

    Certificat client, clé publique et privée :

    cat > client-csr.json <<EOF
    {
      "CN": "client",
      "key": {
          "algo": "ecdsa",
          "size": 256
      },
      "names": [
          {
              "C": "US",
              "ST": "CA",
              "L": "San Francisco"
          }
      ]
    }
    EOF
    
    cfssl gencert \
      -ca=ca.pem \
      -ca-key=ca-key.pem \
      -config=ca-config.json \
      -profile=client client-csr.json | cfssljson -bare client
    

    L’adresse IP du serveur doit être incluse dans la liste des noms alternatifs de sujet pour le certificat du serveur API. Cela garantira que les clients distants peuvent valider le certificat.

    cat > server-csr.json <<EOF
    {
        "CN": "server",
        "key": {
            "algo": "ecdsa",
            "size": 256
        },
        "names": [
            {
                "C": "US",
                "ST": "CA",
                "L": "San Francisco"
            }
        ]
    }
    EOF
    
    cfssl gencert \
      -ca=ca.pem \
      -ca-key=ca-key.pem \
      -config=ca-config.json \
      -hostname=127.0.0.1 \
      -profile=server server-csr.json | cfssljson -bare server
    

    C’est tout pour les certificats.

    Protobuf et gRPC

    Maintenant que les certificats sont prêts, l’étape suivante consiste à créer une définition de schéma pour l’API requise par gRPC. C’est un service simple nommé DiceService, pour montrer comment fonctionnent gRPC et mTLS.

    Ci-dessous le contenu du proto/api.proto déposer. Il définit un point de terminaison RPC RollDie qui accepte RollDieRequest et renvoie la valeur du dé lancé dans le value domaine de RollDieResponse.

    syntax = "proto3";
    
    option go_package = "server/api";
    
    package api;
    
    message RollDieRequest {}
    
    message RollDieResponse {
      int32 value = 1;
    }
    
    service DiceService {
      rpc RollDie (RollDieRequest) returns (RollDieResponse) {}
    }
    

    L’étape suivante consiste à générer du code pour chaque langage à partir de la définition proto à l’aide du compilateur protobuf – Protoc. De plus, chaque langue requiert son propre ensemble de dépendances.

    Installez les packages requis et créez le fichier proto de compilation pour Python :

    pip install grpcio grpcio-tools
    python -m grpc_tools.protoc -I proto --proto_path=proto \
        --python_out=client/api --grpc_python_out=client/api proto/api.proto
    

    Les fichiers compilés se trouvent dans le client/api annuaire. Pour une raison quelconque, le compilateur de protoc pour Python utilise une importation absolue dans le code généré et cela devrait être corrigé :

    cd client/api && cat api_pb2_grpc.py | \
        sed -E 's/^(import api_pb2.*)/from client.api \1/g' > api_pb2_grpc.tmp && \
        mv -f api_pb2_grpc.tmp api_pb2_grpc.py
    

    Installez les modules requis et créez un fichier proto pour Go. protoc-gen-go et protoc-gen-go-grpc par défaut sont installés dans le répertoire GOBIN. Vous pouvez remplacer GOBIN et le pointer vers le répertoire bin de virtualenv – cela facilite le nettoyage par la suite.

    go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
    protoc -I. --go_out=. --go-grpc_out=. proto/api.proto
    

    Les fichiers compilés se trouvent dans le server/api annuaire.

    Client Python

    Pour isoler le reste de la base de code du code Protobuf/gRPC, créez un simple wrapper : api/client.py. Ce wrapper attend un certificat CA, un certificat client et une clé pour établir une connexion TLS à l’adresse fournie.

    import grpc
    
    from . import api_pb2, api_pb2_grpc
    
    class Certs:
      root = None
      cert = None
      key = None
    
      def __init__(self, root, cert, key):
        self.root = open(root, 'rb').read()
        self.cert = open(cert, 'rb').read()
        self.key = open(key, 'rb').read()
    
    class Client:
      rpc = None
    
      def __init__(self, addr: str, crt: Certs):
        creds = grpc.ssl_channel_credentials(crt.root, crt.key, crt.cert)
        channel = grpc.secure_channel(addr, creds)
        self.rpc = api_pb2_grpc.DiceServiceStub(channel)
    
      def roll_die(self) -> int:
        return self.rpc.RollDie(api_pb2.RollDieRequest()).value
    

    Et voici comment utiliser ce client dans la vue de l’application Web. Variable value contient ici le résultat de l’appel RPC.

    
    ICONS = ["?", "⚀", "⚁", "⚂", "⚃", "⚄", "⚅"]
    
    def grpc(request):
      grpc_addr = "127.0.0.1:8443"
    
      crt = api.Certs('certs/ca.pem', 'certs/client.pem', 'certs/client-key.pem')
      try:
        value = api.Client(grpc_addr, crt).roll_die()
      except Exception as e:
        logger.exception(e)
    
      return HttpResponse('Value: ' + ICONS[value])
    

    Maintenant, si vous essayez d’exécuter ce code en démarrant l’application Web et en cliquant sur la vue correspondante, vous obtiendrez une erreur. Et c’est prévu – le serveur n’a pas encore été créé. La partie intéressante ici est l’erreur – elle dira quelque chose sur « échec de la connexion à toutes les adresses », ce qui n’est pas beaucoup. Mais définir la variable d’environnement GRPC_VERBOSITY=debug rend la sortie gRPC plus détaillée et aide beaucoup au dépannage. Cela peut être fait dans le client/settings.py fichier, par exemple :

    if DEBUG:
        os.environ['GRPC_VERBOSITY'] = 'debug'
    

    Aller au serveur

    Implémenter la logique DiceService dans le server/api/server.go. Il initialise le générateur de nombres pseudo-aléatoires et renvoie des valeurs aléatoires comprises entre 1 et 6 sur demande.

    // Number of dots on a die
    const Dots = 6
    
    type Server struct {
        UnimplementedDiceServiceServer
        rnd *rand.Rand
    }
    
    func NewServer() *Server {
        return &Server{
            rnd: rand.New(rand.NewSource(time.Now().UnixNano())),
        }
    }
    
    func (s *Server) RollDie(ctx context.Context, req *RollDieRequest) (*RollDieResponse, error) {
        // rand.Intn returns a value in [0, Dots) interval
        value := s.rnd.Intn(Dots) + 1
        return &RollDieResponse{Value: int32(value)}, nil
    }
    

    La mise en œuvre du service est prête. L’étape suivante consiste à provisionner un serveur gRPC avec des certificats et à le démarrer. Vous pouvez le mettre ici, par exemple :server/server.go Un moment important ici pour activer mTLS est de définir tls.Config{ClientAuth: tls.RequireAndVerifyClientCert} , il indique au serveur de demander et…

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.