|
| 1 | +Notes |
| 2 | +===== |
| 3 | + |
| 4 | +A few development notes and tips for HAProxy Lua ACME implementation. |
| 5 | + |
| 6 | +RFCs |
| 7 | +---- |
| 8 | + |
| 9 | +In addition to standard HTTP, JSON, TLS formats and protocols, knowledge of |
| 10 | +other protocols/formats was necessary to develop ACME client. |
| 11 | + |
| 12 | +* ACME protocol - https://tools.ietf.org/html/draft-ietf-acme-acme-09 |
| 13 | +* JWS (JSON Web Signature) - https://tools.ietf.org/html/rfc7515 |
| 14 | +* JWK (JSON Web Key) - https://tools.ietf.org/html/rfc7517 |
| 15 | + |
| 16 | +Tools |
| 17 | +----- |
| 18 | + |
| 19 | +Aside from `haproxy` and `curl`, additional software was used during |
| 20 | +development, for testing and debugging. |
| 21 | + |
| 22 | +Pebble |
| 23 | +++++++ |
| 24 | + |
| 25 | +peble_ is a test ACME server with intented purpose to help with development of |
| 26 | +ACME clients. It uses slightly different ACME endpoints than referent ACME |
| 27 | +servers (all within specification), to force clients to properly implement |
| 28 | +endpoint discovery. It also presents some other transient challenges to clients |
| 29 | +(e.g. 15% of *nonces* are invalid), with the purpose of creating more robust |
| 30 | +client implementations. |
| 31 | + |
| 32 | +Server is a single binary, with configuration file in JSON format. By default |
| 33 | +it includes a test CA infrastructure, and creates server side certificates on |
| 34 | +the fly. There is also a simple acme client in the distribution, |
| 35 | +`peble-client`, which can be used as a reference/starting point other clients. |
| 36 | + |
| 37 | +Usage |
| 38 | +~~~~~ |
| 39 | +After installing pebble, following their instructions, you can start it with: |
| 40 | + |
| 41 | +:: |
| 42 | + |
| 43 | + cd ~/go/src/github.com/letsencrypt/pebble |
| 44 | + ~/go/bin/pebble |
| 45 | + |
| 46 | +It loads the configuration and certificates from `test` subdirectory, and binds |
| 47 | +to port 14000 by default. |
| 48 | + |
| 49 | + |
| 50 | +Boulder |
| 51 | ++++++++ |
| 52 | + |
| 53 | +boulder_ is a reference ACME server, used by Let's Encrypt organization. One |
| 54 | +can run it locally, via Docker Compose, for easier testing and validation. |
| 55 | + |
| 56 | +MitmProxy |
| 57 | ++++++++++ |
| 58 | + |
| 59 | +ACME protocol v2 mandates that clients use TLS to access the ACME server. That |
| 60 | +means we can't easily track request flow from ACME client to server (e.g. to |
| 61 | +debug our own client, or to observe `peble-client` behaviour). Unfortunately, |
| 62 | +while `wireshark` can support/decrypt static SSL traffic, it doesn't support |
| 63 | +Diffie-Hellman key exchanges, so decrypting standard TLS traffic is impossible. |
| 64 | + |
| 65 | +mitmproxy_ is a TLS capable HTTP proxy for software developers and penetration |
| 66 | +testers. We can use it to observe traffic between local ACME client and local |
| 67 | +ACME server. |
| 68 | + |
| 69 | +Usage |
| 70 | +~~~~~ |
| 71 | + |
| 72 | +When you run peble it will create server key and cert in |
| 73 | +`~/go/src/github.com/letsencrypt/pebble/test/certs/localhost`. You need to |
| 74 | +concat test key and cert into single file (`mitm.pem` in our example) |
| 75 | + |
| 76 | +:: |
| 77 | + |
| 78 | + sudo mitmweb -R https://localhost:14000 -p 443 --no-browser --insecure --cert localhost=~/go/src/github.com/letsencrypt/pebble/test/certs/localhost/mitm.pem |
| 79 | + Proxy server listening at http://0.0.0.0:443/ |
| 80 | + Web server listening at http://127.0.0.1:8081/ |
| 81 | + |
| 82 | +For the time being, it is necesarry to run mitmproxy with sudo, binding to port |
| 83 | +443, even if we'd like to run it on some nonprivileged port, e.g. 8443 |
| 84 | + |
| 85 | +:: |
| 86 | + |
| 87 | + sudo mitmweb -R https://localhost:14000 -p 8443 --no-browser --insecure --cert localhost=~/go/src/github.com/letsencrypt/pebble/test/certs/localhost/mitm.pem |
| 88 | + Proxy server listening at http://0.0.0.0:443/ |
| 89 | + Web server listening at http://127.0.0.1:8081/ |
| 90 | + |
| 91 | +When we run discovery query against the ACME server (through mitmproxy), we |
| 92 | +get this: |
| 93 | + |
| 94 | +:: |
| 95 | + |
| 96 | + curl -k https://localhost:8443/dir |
| 97 | + { |
| 98 | + "meta": { |
| 99 | + "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" |
| 100 | + }, |
| 101 | + "newAccount": "https://localhost/sign-me-up", |
| 102 | + "newNonce": "https://localhost/nonce-plz", |
| 103 | + "newOrder": "https://localhost/order-plz" |
| 104 | + } |
| 105 | + |
| 106 | +were we'd expect to get |
| 107 | + |
| 108 | +:: |
| 109 | + |
| 110 | + { |
| 111 | + "meta": { |
| 112 | + "termsOfService": "data:text/plain,Do%20what%20thou%20wilt" |
| 113 | + }, |
| 114 | + "newAccount": "https://localhost:8443/sign-me-up", |
| 115 | + "newNonce": "https://localhost:8443/nonce-plz", |
| 116 | + "newOrder": "https://localhost:8443/order-plz" |
| 117 | + } |
| 118 | + |
| 119 | +mitmproxy doesn't pass full host info to the upstream, i.e. it doesn't respect |
| 120 | +HTTP 1.1 RFC (https://tools.ietf.org/html/rfc7230#section-5.4), where `Host` |
| 121 | +header **must** include hostname and port if port is not 80 (for http) or 443 |
| 122 | +(for https). Hence, we force mitmproxy to run on standard HTTPS port, where |
| 123 | +explicit port is not required. |
| 124 | + |
| 125 | +.. _peble: https://github.com/letsencrypt/pebble |
| 126 | +.. _boulder: https://github.com/letsencrypt/boulder |
| 127 | +.. _mitmproxy: https://mitmproxy.org |
0 commit comments