forked from TrueCloudLab/certificates
Merge pull request #41 from smallstep/hello-mtls-python
Hello mtls python client + gunicorn server
This commit is contained in:
commit
98b3d971f6
10 changed files with 215 additions and 18 deletions
|
@ -50,6 +50,30 @@ This matrix shows the set of features we'd like to demonstrate in each language
|
||||||
and where each language is. Bug fixes, improvements, and examples in new
|
and where each language is. Bug fixes, improvements, and examples in new
|
||||||
languages are appreciated!
|
languages are appreciated!
|
||||||
|
|
||||||
|
[curl/](curl/)
|
||||||
|
- [X] Client
|
||||||
|
- [X] mTLS (send client certificate if server asks for it)
|
||||||
|
- [X] Automatic certificate rotation
|
||||||
|
- [ ] Restrict to safe ciphersuites and TLS versions
|
||||||
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
|
- [ ] Root certificate rotation
|
||||||
|
|
||||||
|
[nginx/](nginx/)
|
||||||
|
- [X] Server
|
||||||
|
- [X] mTLS (client authentication using internal root certificate)
|
||||||
|
- [X] Automatic certificate renewal
|
||||||
|
- [X] Restrict to safe ciphersuites and TLS versions
|
||||||
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
|
- [ ] Root certificate rotation
|
||||||
|
|
||||||
|
[envoy/](envoy/)
|
||||||
|
- [X] Server
|
||||||
|
- [X] mTLS (client authentication using internal root certificate)
|
||||||
|
- [X] Automatic certificate renewal
|
||||||
|
- [X] Restrict to safe ciphersuites and TLS versions
|
||||||
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
|
- [ ] Root certificate rotation
|
||||||
|
|
||||||
[go/](go/)
|
[go/](go/)
|
||||||
- [X] Server using autocert certificate & key
|
- [X] Server using autocert certificate & key
|
||||||
- [X] mTLS (client authentication using internal root certificate)
|
- [X] mTLS (client authentication using internal root certificate)
|
||||||
|
@ -78,22 +102,6 @@ languages are appreciated!
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
- [ ] Root certificate rotation
|
- [ ] Root certificate rotation
|
||||||
|
|
||||||
[curl/](curl/)
|
|
||||||
- [X] Client
|
|
||||||
- [X] mTLS (send client certificate if server asks for it)
|
|
||||||
- [X] Automatic certificate rotation
|
|
||||||
- [ ] Restrict to safe ciphersuites and TLS versions
|
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
|
||||||
- [ ] Root certificate rotation
|
|
||||||
|
|
||||||
[nginx/](nginx/)
|
|
||||||
- [X] Server
|
|
||||||
- [X] mTLS (client authentication using internal root certificate)
|
|
||||||
- [X] Automatic certificate renewal
|
|
||||||
- [X] Restrict to safe ciphersuites and TLS versions
|
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
|
||||||
- [ ] Root certificate rotation
|
|
||||||
|
|
||||||
[node/](node/)
|
[node/](node/)
|
||||||
- [X] Server
|
- [X] Server
|
||||||
- [X] mTLS (client authentication using internal root certificate)
|
- [X] mTLS (client authentication using internal root certificate)
|
||||||
|
@ -108,10 +116,16 @@ languages are appreciated!
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
- [ ] Root certificate rotation
|
- [ ] Root certificate rotation
|
||||||
|
|
||||||
[envoy/](envoy/)
|
[py-gunicorn/](py-gunicorn/)
|
||||||
- [X] Server
|
- [X] Server (gunicorn + Flask)
|
||||||
- [X] mTLS (client authentication using internal root certificate)
|
- [X] mTLS (client authentication using internal root certificate)
|
||||||
- [X] Automatic certificate renewal
|
- [X] Automatic certificate renewal
|
||||||
- [X] Restrict to safe ciphersuites and TLS versions
|
- [X] Restrict to safe ciphersuites and TLS versions
|
||||||
- [ ] TLS stack configuration loaded from `step-ca`
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
- [ ] Root certificate rotation
|
- [ ] Root certificate rotation
|
||||||
|
- [X] Client using autocert root certificate (python)
|
||||||
|
- [X] mTLS (send client certificate if server asks for it)
|
||||||
|
- [X] Automatic certificate rotation
|
||||||
|
- [X] Restrict to safe ciphersuites and TLS versions
|
||||||
|
- [ ] TLS stack configuration loaded from `step-ca`
|
||||||
|
- [ ] Root certificate rotation
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
RUN mkdir /src
|
||||||
|
|
||||||
|
ADD client.py /src
|
||||||
|
ADD client.requirements.txt /src
|
||||||
|
RUN pip3 install -r /src/client.requirements.txt
|
||||||
|
|
||||||
|
CMD ["python", "/src/client.py"]
|
14
autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server
Normal file
14
autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
RUN mkdir /src
|
||||||
|
|
||||||
|
# Gunicorn configuration
|
||||||
|
ADD gunicorn.conf /src
|
||||||
|
|
||||||
|
# Flask app
|
||||||
|
ADD server.py /src
|
||||||
|
ADD requirements.txt /src
|
||||||
|
RUN pip3 install -r /src/requirements.txt
|
||||||
|
|
||||||
|
# app, certificate watcher and envoy
|
||||||
|
CMD ["gunicorn", "--config", "/src/gunicorn.conf", "--pythonpath", "/src", "server:app"]
|
79
autocert/examples/hello-mtls/py-gunicorn/client.py
Normal file
79
autocert/examples/hello-mtls/py-gunicorn/client.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import ssl
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import http.client
|
||||||
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
ca_certs = '/var/run/autocert.step.sm/root.crt'
|
||||||
|
cert_file = '/var/run/autocert.step.sm/site.crt'
|
||||||
|
key_file = '/var/run/autocert.step.sm/site.key'
|
||||||
|
|
||||||
|
# RenewHandler is an even file system event handler that reloads the certs in
|
||||||
|
# the context when a file is modified.
|
||||||
|
class RenewHandler(FileSystemEventHandler):
|
||||||
|
def __init__(self, ctx):
|
||||||
|
self.ctx = ctx
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def on_modified(self, event):
|
||||||
|
logging.info("reloading certs ...")
|
||||||
|
ctx.load_cert_chain(cert_file, key_file)
|
||||||
|
|
||||||
|
# Monitor is a thread that watches for changes in a path and calls to the
|
||||||
|
# RenewHandler when a file is modified.
|
||||||
|
class Monitor(threading.Thread):
|
||||||
|
def __init__(self, handler, path):
|
||||||
|
super().__init__()
|
||||||
|
self.handler = handler
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
observer = Observer()
|
||||||
|
observer.schedule(self.handler, self.path)
|
||||||
|
observer.start()
|
||||||
|
|
||||||
|
# Signal handler
|
||||||
|
def handler(signum, frame):
|
||||||
|
print("exiting ...")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
|
||||||
|
|
||||||
|
# Start signal handler to exit
|
||||||
|
signal.signal(signal.SIGTERM, handler)
|
||||||
|
|
||||||
|
# url from the environment
|
||||||
|
url = urlparse(os.environ['HELLO_MTLS_URL'])
|
||||||
|
|
||||||
|
# ssl context
|
||||||
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||||
|
ctx.set_ciphers('ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256')
|
||||||
|
ctx.load_verify_locations(ca_certs)
|
||||||
|
ctx.load_cert_chain(cert_file, key_file)
|
||||||
|
|
||||||
|
# initialize the renewer with the ssl context
|
||||||
|
renewer = RenewHandler(ctx)
|
||||||
|
|
||||||
|
# start file monitor
|
||||||
|
monitor = Monitor(renewer, os.path.dirname(cert_file))
|
||||||
|
monitor.start()
|
||||||
|
|
||||||
|
# Do requests
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
conn = http.client.HTTPSConnection(url.netloc, context=ctx)
|
||||||
|
conn.request("GET", url.path)
|
||||||
|
r = conn.getresponse()
|
||||||
|
data = r.read()
|
||||||
|
logging.info("%d - %s - %s", r.status, r.reason, data)
|
||||||
|
except Exception as err:
|
||||||
|
print('Something went wrong:', err)
|
||||||
|
time.sleep(5)
|
|
@ -0,0 +1 @@
|
||||||
|
watchdog
|
14
autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf
Normal file
14
autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
bind = '0.0.0.0:443'
|
||||||
|
workers = 2
|
||||||
|
accesslog = '-'
|
||||||
|
|
||||||
|
# mTLS configuration with TLSv1.2 and requiring and validating client
|
||||||
|
# certificates
|
||||||
|
ssl_version = 5 # ssl.PROTOCOL_TLSv1_2
|
||||||
|
cert_reqs = 2 # ssl.CERT_REQUIRED
|
||||||
|
ciphers = 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256'
|
||||||
|
ca_certs = '/var/run/autocert.step.sm/root.crt'
|
||||||
|
certfile = '/var/run/autocert.step.sm/site.crt'
|
||||||
|
keyfile = '/var/run/autocert.step.sm/site.key'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: hello-mtls-client
|
||||||
|
labels: {app: hello-mtls-client}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector: {matchLabels: {app: hello-mtls-client}}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
autocert.step.sm/name: hello-mtls-client.default.pod.cluster.local
|
||||||
|
labels: {app: hello-mtls-client}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: hello-mtls-client
|
||||||
|
image: hello-mtls-client-py-gunicorn:latest
|
||||||
|
imagePullPolicy: Never
|
||||||
|
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
||||||
|
env:
|
||||||
|
- name: HELLO_MTLS_URL
|
||||||
|
value: https://hello-mtls.default.svc.cluster.local
|
|
@ -0,0 +1,33 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels: {app: hello-mtls}
|
||||||
|
name: hello-mtls
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 443
|
||||||
|
targetPort: 443
|
||||||
|
selector: {app: hello-mtls}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: hello-mtls
|
||||||
|
labels: {app: hello-mtls}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector: {matchLabels: {app: hello-mtls}}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
autocert.step.sm/name: hello-mtls.default.svc.cluster.local
|
||||||
|
labels: {app: hello-mtls}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: hello-mtls
|
||||||
|
image: hello-mtls-server-py-gunicorn:latest
|
||||||
|
imagePullPolicy: Never
|
||||||
|
resources: {requests: {cpu: 10m, memory: 20Mi}}
|
|
@ -0,0 +1,2 @@
|
||||||
|
Flask
|
||||||
|
gunicorn
|
9
autocert/examples/hello-mtls/py-gunicorn/server.py
Normal file
9
autocert/examples/hello-mtls/py-gunicorn/server.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def hello():
|
||||||
|
return "Hello World!\n"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host='127.0.0.1', port=8080, debug=False)
|
Loading…
Reference in a new issue