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,21 +50,31 @@ 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! -[go/](go/) -- [X] Server using autocert certificate & key +[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 -- [X] Client using autocert root certificate - - [X] mTLS (send client certificate if server asks for it) - - [X] Automatic 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-grpc/](go-grpc/) +[go/](go/) - [X] Server using autocert certificate & key - [X] mTLS (client authentication using internal root certificate) - [X] Automatic certificate renewal @@ -78,21 +88,19 @@ 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 +[go-grpc/](go-grpc/) +- [X] Server using autocert certificate & key - [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 + - [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 [node/](node/) - [X] Server @@ -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 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/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/client.py b/autocert/examples/hello-mtls/py-gunicorn/client.py new file mode 100644 index 00000000..c84963b2 --- /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().__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) 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 new file mode 100644 index 00000000..cab27a43 --- /dev/null +++ b/autocert/examples/hello-mtls/py-gunicorn/gunicorn.conf @@ -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' + + 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 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)