Merge pull request #41 from smallstep/hello-mtls-python

Hello mtls python client + gunicorn server
This commit is contained in:
Mariano Cano 2019-02-27 16:38:25 -08:00 committed by GitHub
commit 98b3d971f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 215 additions and 18 deletions

View file

@ -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

View file

@ -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"]

View 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"]

View 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)

View file

@ -0,0 +1 @@
watchdog

View 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'

View file

@ -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

View file

@ -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}}

View file

@ -0,0 +1,2 @@
Flask
gunicorn

View 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)