Configurer un serveur HTTPS simple avec Python 3

Aperçu

Le serveur HTTP de Python est un moyen pratique d'expérimenter le code JavaScript dans un navigateur. HTTPS est requis pour la Progressive Web App (PWA), qui est au centre de la communauté JavaScript depuis 2016, car il n'y avait pas d'autre article sur la modification du module ssl sur le serveur HTTPS de Python et la série Python 3. , J'ai décidé de résumer ce que j'avais enquêté.

supposition

En supposant Python 3.6 et OpenSSL 1.0.2 et versions ultérieures. Diverses méthodes et constantes ont été ajoutées au module ssl de la série Python 3, et l'exemple de code peut ne pas fonctionner sur Python 3.6 et les versions antérieures.

Vous pouvez trouver la version d'OpenSSL à partir de la constante de ssl.

>>> import ssl
>>> ssl.OPENSSL_VERSION

Examen du serveur HTTP

Si vous souhaitez démarrer le serveur qui fournit le fichier HTML à partir de la ligne de commande, exécutez la commande suivante:

python3 -m http.server 8000

Si vous souhaitez utiliser CGI, spécifiez l'option --cgi.

python3 -m http.server --cgi 8000

Le script que vous voulez exécuter doit être dans / cgi-bin ou / htbin.

#!/usr/bin/env python3

print("Content-Type: text/plain; charset=utf-8;\r\n")
print("hello world")

Certificat auto-signé et génération de clé privée

Vous pouvez générer un certificat auto-signé et une clé privée avec le one-liner suivant.

# https://stackoverflow.com/a/41366949/531320
openssl req -x509 -newkey rsa:4096 -sha256 \
-nodes -keyout server.key -out server.crt \
-subj "/CN=example.com" -days 3650

Configurer un serveur HTTPS

En plus du module http.server, vous devez charger le module ssl. Créez un objet SSLContext, spécifiez divers paramètres, puis utilisez SSLContext.wrap_socket pour créer un objet SSLSocket.

server.py


from http.server import HTTPServer, SimpleHTTPRequestHandler
import ssl

def run(host, port, ctx, handler):
    server = HTTPServer((host, port), handler)
    server.socket = ctx.wrap_socket(server.socket)
    print('Server Starts - %s:%s' % (host, port))

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    server.server_close()
    print('Server Stops - %s:%s' % (host, port))

if __name__ == '__main__':
    host = 'localhost'
    port = 8000

    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ctx.load_cert_chain('server.crt', keyfile='server.key')
    ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
    handler = SimpleHTTPRequestHandler

    run(host, port, ctx, handler)

ssl.OP_NO_TLSv1 et ssl.OP_NO_TLSv1_1 signifient pour désactiver TLS 1.0 et TLS 1.1, respectivement.

Prend en charge CGI

Pour CGI, utilisez CGIHTTPRequestHandler au lieu de SimpleHTTPRequestHandler.

server.py


from http.server import HTTPServer, CGIHTTPRequestHandler
import ssl

def run(host, port, ctx, handler):
    server = HTTPServer((host, port), handler)
    server.socket = ctx.wrap_socket(server.socket)
    print('Server Starts - %s:%s' % (host, port))

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    server.server_close()
    print('Server Stops - %s:%s' % (host, port))

if __name__ == '__main__':
    host = 'localhost'
    port = 8000

    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ctx.load_cert_chain('server.crt', keyfile='server.key')
    ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1

    handler = CGIHTTPRequestHandler
    handler.cgi_directories = ['/cgi-bin', '/htbin']
    # https://stackoverflow.com/a/27303995/531320
    handler.have_fork=False

    run(host, port, ctx, handler)

Pour macOS et Unix, le serveur ne fonctionnera pas sans ajouter handler.have_fork = False.

L'avantage de CGI est qu'il peut exécuter des programmes dans des langages autres que Python. Le code Node.js ressemble à ceci:

node.cgi


#!/usr/bin/env node

console.log("Content-type: text/plain; charset=utf-8\n");
console.log("Hello World");

Prend en charge WSGI

Vous pouvez utiliser le module wsgiref pour configurer un serveur compatible WSGI.

server.py


from wsgiref.simple_server import make_server
from pathlib import Path
import ssl

def simple_app(env, start_response):
    info = env['PATH_INFO'][1:]

    if info == '':
        info = 'index.html'

    root = Path.cwd()
    path = root.joinpath(info).resolve()

    if root in path.parents and path.is_file():
        status = '200 OK'
        body = path.read_bytes()
        suffix = path.suffix
        content_type = {
          '.html': 'text/html',
          '.txt': 'text/plain',
          '.json': 'application/json'
        }.get(suffix, 'text/plain')
    else :
        body = b'404 Not Found'
        status = '404 Not Found'
        content_type = 'text/plain'

    headers = [
       ('Content-Type', content_type),
       ('Content-Length', str(len(body)))
    ]

    start_response(status, headers)
    return [body]

def run(host, port, ctx, app):
    server = make_server(host, port, app)
    server.socket = ctx.wrap_socket(server.socket)
    print('Server Starts - %s:%s' % (host, port))

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    server.server_close()
    print('Server Stops - %s:%s' % (host, port))

if __name__ == '__main__':
    host = 'localhost'
    port = 8000
    ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ctx.load_cert_chain('server.crt', keyfile='server.key')
    ctx.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1

    run(host, port, ctx, simple_app)

Si vous voulez connaître toutes les variables d'environnement définies (ʻenv), allez dans [PEP-333](https://www.python.org/dev/peps/pep-0333/#environ-variables) Se il vous plaît se référer. Vous pouvez également vérifier à partir de la page affichée lorsque wsgiref.simple_server.demo_app` devient une application d'exécution.

SSLContext

produire

ssl.create_default_context est recommandé pour un usage général.

ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)

Les constantes que vous pouvez spécifier sont Purpose.SERVER_AUTH (utilisé pour créer un socket côté client) et Purpose.CLIENT_AUTH (utilisé pour créer un socket côté serveur).

Vous pouvez également utiliser directement le constructeur SSLContext.

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

Les constantes que vous pouvez spécifier sont PROTOCOL_TLS (par défaut), PROTOCOL_TLS_CLIENT et PROTOCOL_TLS_SERVER. Diverses constantes disponibles dans Python 3.6 et versions antérieures sont obsolètes.

option

Vous pouvez vérifier ou ajouter des paramètres via les options. Vous pouvez voir les options par défaut comme suit:

>>> import ssl
>>> ssl.create_default_context().options
<Options.OP_ALL|OP_NO_SSLv3|OP_NO_SSLv2|
OP_CIPHER_SERVER_PREFERENCE|
OP_SINGLE_DH_USE|OP_SINGLE_ECDH_USE|
OP_NO_COMPRESSION: 2203714559>

Le code suivant donne le même résultat.

>>> ssl.SSLContext().options

Les options par défaut de Python 3.6 sont:

Vous pouvez accéder ou modifier les options de contexte après avoir encapsulé un objet socket avec wrap_socket.

server = HTTPServer((host, port), handler)
server.socket = ctx.wrap_socket(server.socket)
print(server.socket.context.options)

Différence entre deux wrap_socket

Les arguments qui peuvent être spécifiés pour ssl.wrap_socket et SSLContext.wrap_socket sont différents. Les arguments de Python 3.6 sont:

ssl.wrap_socket(
  sock,
  keyfile=None,
  certfile=None,
  server_side=False,
  cert_reqs=CERT_NONE,
  ssl_version={see docs},
  ca_certs=None,
  do_handshake_on_connect=True,
  suppress_ragged_eofs=True,
  ciphers=None
)

SSLContext.wrap_socket(
  sock,
  server_side=False,
  do_handshake_on_connect=True,
  suppress_ragged_eofs=True,
  server_hostname=None,
  session=None
)

La différence entre les deux est que SSLContext.wrap_socket a moins d'options à spécifier. La session ajoutée à SSLContext.wrap_socket dans Python 3.6 ne peut pas être spécifiée dans ssl.wrap_socket.

La norme d'utilisation de ssl.wrap_socket et SSLContext.wrap_socket est de savoir s'il y a beaucoup ou peu de paramètres.

Si vous utilisez ssl.wrap.socket, les paramètres seront modifiés via server.socket.context, donc un grand nombre de paramètres rendront le code difficile à lire.

Un autre avantage de la création directe d'un objet SSLContext est qu'il peut être utilisé pour une bibliothèque HTTP tierce.

Suite cryptographique

Les suites de chiffrement peuvent être trouvées avec get_ciphers. Utilisez set_ciphers pour changer la suite de chiffrement.

>>> import ssl
>>> ctx = ssl.create_default_context().get_ciphers()
>>> ctx.set_ciphers('ECDHE+AESGCM:!ECDSA')
>>> ctx.get_ciphers()

Une suite de chiffrement recommandée est disponible sur le site de Mozilla (https://wiki.mozilla.org/Security/Server_Side_TLS). Cloudflare a publié un fichier de configuration nginx (https://github.com/cloudflare/sslconfig) pour TLS 1.3. OpenSSL 1.1.0 fait partie de TLS 1.3 et sera entièrement pris en charge dans OpenSSL 1.1.1.

Autre

Version minimale de TLS

La norme PCI DSS v3.2, qui est utilisée pour les services de paiement par carte de crédit, exige que SSL et TSL 1.0 soient désactivés jusqu'à la fin juin 2018. Rapidement, le service CDN utilisé par PyPI, désactive également TLS 1.1.

Vous pouvez trouver la version de TLS prise en charge par Python 3 installée sur votre système dans la ligne suivante.

# https://news.ycombinator.com/item?id=13539034
python3 -c "import json, urllib.request; print(json.loads(urllib.request.urlopen('https://www.howsmyssl.com/a/check').read().decode('UTF-8'))['tls_version'])"

Proposition d'une nouvelle API pour TLS

PEP 543 propose l'introduction d'une nouvelle API pour TLS. Les motivations de développement incluent le fait que le module ssl dépend d'OpenSSL, ce qui signifie que Python doit être recompilé pour introduire le nouvel OpenSSL et qu'il n'est pas possible de basculer vers différentes bibliothèques TLS différentes d'OpenSSL. Je vais.

Les principales bibliothèques SSL / TLS sont:

Recommended Posts

Configurer un serveur HTTPS simple avec Python 3
Configurer un serveur SMTP simple en Python
Configurer un serveur HTTPS simple avec asyncio
Configurez un serveur SMTP de test en Python.
[Vagrant] Configurer un serveur API simple avec python
Configurer un serveur UDP en langage C
Comment configurer un serveur SMTP simple qui peut être testé localement en Python
Configurez un serveur Web local en 30 secondes en utilisant http.server en python 3
Configurer un serveur local simple sur votre Mac
Configurez un serveur gratuit sur AWS en 30 minutes
Implémentation d'un algorithme simple en Python 2
Exécutez un algorithme simple en Python
Un client HTTP simple implémenté en Python
Configurer un serveur Samba avec Docker
Essayez de dessiner une animation simple en Python
Créer une application GUI simple en Python
Configurer un serveur de messagerie avec Twisted
Ecrire une méthode de cupidité simple en Python
Ecrire un plugin Vim simple en Python 3
GRPC simple en Python
Configurer un serveur local avec le téléchargement Go-File-
Envoyez du courrier avec mailx à un serveur SMTP factice configuré avec python.
Démarrez un serveur Web Python simple avec Docker
Notes de programme simples Pub / Sub en Python
Configurer un serveur local avec le téléchargement Go-File-
Créer un modèle d'investissement dynamique simple en Python
Comment configurer un serveur de développement local
Configurer un environnement de développement Python sur Marvericks
Serveur DNS en Python ....
Configurer un serveur SMTP factice avec Python et vérifier le fonctionnement de l'envoi depuis Action Mailer
Introduction et utilisation de la bouteille Python ・ Essayez de configurer un serveur Web simple avec une fonction de connexion
Mettez Docker dans Windows Home et exécutez un serveur Web simple avec Python
Ecrire un programme de dynamique moléculaire super simple en python
Comment configurer un environnement Python à l'aide de pyenv
Configurer un serveur de ressources Minecraft (Spigot) via docker (2)
Configurer un serveur de fichiers sur Ubuntu 20.04 à l'aide de Samba
Créez un Slackbot simple avec un bouton interactif en python
[Partie 1] Configurons un serveur Micra sur Linux
Configurer un serveur de ressources Minecraft (Spigot) via docker
Créez un faux serveur Minecraft en Python avec Quarry
Configurer un environnement de développement Python avec Sublime Text 2
Définir le proxy pour Python pip (décrit dans pip.ini)
Configurer Python 3.4 sur Ubuntu
Prendre une capture d'écran en Python
Créer une fonction en Python
Créer un dictionnaire en Python
Créer un serveur REST (simple)
Configurer pour Mac (Python)
Créer un bookmarklet en Python
Analyse de régression simple avec Python
Serveur HTTP simple pour python
Créer un serveur textlint simple
Dessinez un cœur en Python
Client IRC simple avec python
Configurer Nunjucks dans Node.js
Définir le test python dans jenkins
Je veux configurer un serveur fictif pour python-flask en quelques secondes en utilisant swagger-codegen.
Hello World avec un serveur Web simple qui suit WSGI (Web Server Gateway Interface) en Python