From b140fe63bd2684b8ea5920bf806087d3443cad35 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 22 Feb 2019 10:47:37 -0800 Subject: [PATCH 1/4] Add a gunicorn server with a flask app using mTLS Fixes smallstep/ca-component#139 --- .../hello-mtls/py-gunicorn/Dockerfile.server | 14 ++++++++ .../hello-mtls/py-gunicorn/gunicorn.conf | 13 ++++++++ .../py-gunicorn/hello-mtls.server.yaml | 33 +++++++++++++++++++ .../hello-mtls/py-gunicorn/requirements.txt | 2 ++ .../examples/hello-mtls/py-gunicorn/server.py | 9 +++++ 5 files changed, 71 insertions(+) create mode 100644 autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server create mode 100644 autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf create mode 100644 autocert/examples/hello-mtls/py-gunicorn/hello-mtls.server.yaml create mode 100644 autocert/examples/hello-mtls/py-gunicorn/requirements.txt create mode 100644 autocert/examples/hello-mtls/py-gunicorn/server.py diff --git a/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server b/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server new file mode 100644 index 00000000..d99c972f --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.server @@ -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"] diff --git a/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf b/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf new file mode 100644 index 00000000..1b6b7490 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf @@ -0,0 +1,13 @@ +bind = '0.0.0.0:443' +workers = 2 + +# 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' + + diff --git a/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.server.yaml b/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.server.yaml new file mode 100644 index 00000000..14e675df --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.server.yaml @@ -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}} diff --git a/autocert/examples/hello-mtls/py-gunicorn/requirements.txt b/autocert/examples/hello-mtls/py-gunicorn/requirements.txt new file mode 100644 index 00000000..cef5a165 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/requirements.txt @@ -0,0 +1,2 @@ +Flask +gunicorn diff --git a/autocert/examples/hello-mtls/py-gunicorn/server.py b/autocert/examples/hello-mtls/py-gunicorn/server.py new file mode 100644 index 00000000..7e44425f --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/server.py @@ -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) From c0992c717ccda5a98cecfcd7ad528b57a3427254 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 22 Feb 2019 13:55:24 -0800 Subject: [PATCH 2/4] Add example of a python client Fixes smallstep/ca-component#139 --- .../hello-mtls/py-gunicorn/Dockerfile.client | 9 +++ .../examples/hello-mtls/py-gunicorn/client.py | 79 +++++++++++++++++++ .../py-gunicorn/client.requirements.txt | 1 + .../hello-mtls/py-gunicorn/gunicorn.conf | 1 + .../py-gunicorn/hello-mtls.client.yaml | 22 ++++++ 5 files changed, 112 insertions(+) create mode 100644 autocert/examples/hello-mtls/py-gunicorn/Dockerfile.client create mode 100644 autocert/examples/hello-mtls/py-gunicorn/client.py create mode 100644 autocert/examples/hello-mtls/py-gunicorn/client.requirements.txt create mode 100644 autocert/examples/hello-mtls/py-gunicorn/hello-mtls.client.yaml diff --git a/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.client b/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.client new file mode 100644 index 00000000..dd6dbf08 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/Dockerfile.client @@ -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"] diff --git a/autocert/examples/hello-mtls/py-gunicorn/client.py b/autocert/examples/hello-mtls/py-gunicorn/client.py new file mode 100644 index 00000000..c8ba3da3 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/client.py @@ -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() + + 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): + threading.Thread.__init__(self) + 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) diff --git a/autocert/examples/hello-mtls/py-gunicorn/client.requirements.txt b/autocert/examples/hello-mtls/py-gunicorn/client.requirements.txt new file mode 100644 index 00000000..c1cb094b --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/client.requirements.txt @@ -0,0 +1 @@ +watchdog \ No newline at end of file diff --git a/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf b/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf index 1b6b7490..cab27a43 100644 --- a/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf +++ b/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf @@ -1,5 +1,6 @@ bind = '0.0.0.0:443' workers = 2 +accesslog = '-' # mTLS configuration with TLSv1.2 and requiring and validating client # certificates diff --git a/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.client.yaml b/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.client.yaml new file mode 100644 index 00000000..370c2634 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/hello-mtls.client.yaml @@ -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 From 238657ce3a6316ff16d176161ffef879ba571bc5 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 22 Feb 2019 14:01:32 -0800 Subject: [PATCH 3/4] Update readme. --- autocert/examples/hello-mtls/README.md | 50 ++++++++++++++++---------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/autocert/examples/hello-mtls/README.md b/autocert/examples/hello-mtls/README.md index 7fb95093..5fa9cdec 100644 --- a/autocert/examples/hello-mtls/README.md +++ b/autocert/examples/hello-mtls/README.md @@ -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 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/) - [X] Server using autocert certificate & key - [X] mTLS (client authentication using internal root certificate) @@ -78,22 +102,6 @@ languages are appreciated! - [ ] TLS stack configuration loaded from `step-ca` - [ ] 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/) - [X] Server - [X] mTLS (client authentication using internal root certificate) @@ -108,10 +116,16 @@ languages are appreciated! - [ ] TLS stack configuration loaded from `step-ca` - [ ] Root certificate rotation -[envoy/](envoy/) -- [X] Server +[py-gunicorn/](py-gunicorn/) +- [X] Server (gunicorn + Flask) - [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 +- [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 From f04e6fdff7d06d714103e4d17a111425ffd26224 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 27 Feb 2019 16:32:41 -0800 Subject: [PATCH 4/4] Use super().__init__() --- autocert/examples/hello-mtls/py-gunicorn/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autocert/examples/hello-mtls/py-gunicorn/client.py b/autocert/examples/hello-mtls/py-gunicorn/client.py index c8ba3da3..c84963b2 100644 --- a/autocert/examples/hello-mtls/py-gunicorn/client.py +++ b/autocert/examples/hello-mtls/py-gunicorn/client.py @@ -20,7 +20,7 @@ key_file = '/var/run/autocert.step.sm/site.key' class RenewHandler(FileSystemEventHandler): def __init__(self, ctx): self.ctx = ctx - super() + super().__init__() def on_modified(self, event): logging.info("reloading certs ...") @@ -30,7 +30,7 @@ class RenewHandler(FileSystemEventHandler): # RenewHandler when a file is modified. class Monitor(threading.Thread): def __init__(self, handler, path): - threading.Thread.__init__(self) + super().__init__() self.handler = handler self.path = path