Browse Source

Initial version

master
Chris Smith 8 years ago
commit
c6d8eae4e1
4 changed files with 124 additions and 0 deletions
  1. 11
    0
      Dockerfile
  2. 56
    0
      README.md
  3. 23
    0
      fetcher.py
  4. 34
    0
      generate.py

+ 11
- 0
Dockerfile View File

@@ -0,0 +1,11 @@
1
+FROM python:3.5.1-alpine 
2
+MAINTAINER Chris Smith <chris87@gmail.com> 
3
+
4
+RUN \
5
+  pip install \
6
+    python-etcd
7
+
8
+COPY *.py /
9
+
10
+VOLUME ["/letsencrypt"]
11
+ENTRYPOINT ["python", "/generate.py"]

+ 56
- 0
README.md View File

@@ -0,0 +1,56 @@
1
+# Automatic Let's Encrypt certificate generator 
2
+
3
+This connects my [docker-service-reporter](https://github.com/csmith/docker-service-reporter/)
4
+and [docker-letsencrypt-lexicon](https://github.com/csmith/docker-letsencrypt-lexicon)
5
+containers together. Between the three, they create a pipeline
6
+to automatically obtain Let's Encrypt certificates for
7
+containers as they're added or modified. 
8
+
9
+## How? 
10
+
11
+The `service-reporter` container populates `etcd` with details about
12
+known containers.
13
+
14
+This container monitors `etcd` for a label specifying vhosts, and builds a
15
+list of domain names and alternatives that need certificates.
16
+
17
+Finally, `letsencrypt-lexicon` takes in the list of domain names and
18
+obtains the actual certificates for them.
19
+
20
+## Usage
21
+
22
+Create a named volume to use for the domains list and resulting
23
+certificates:
24
+
25
+```bash
26
+docker volume create --name letsencrypt-data
27
+```
28
+
29
+You should mount this volume in the `letsencrypt-lexicon` container at
30
+`/letsencrypt`.
31
+
32
+Then run this container. It takes the same arguments as `service-reporter`:
33
+
34
+```
35
+  --etcd-host (default: etcd) hostname where ectd is running
36
+  --etcd-port (default: 2379) port to connect to ectd on
37
+  --etcd-prefix (default: /docker) prefix to read keys from
38
+  --name (default: unknown) name of the host running docker
39
+```
40
+
41
+So running the container will look something like:
42
+
43
+```bash
44
+docker run -d \
45
+  --name service-letsencrypt \
46
+  --restart always \
47
+  -v letsencrypt-data:/letsencrypt \
48
+  csmith/service-letsencrypt:latest \
49
+  --<arguments>
50
+```
51
+
52
+## Current known issues
53
+
54
+* **The container performs one update and then exits.** It does not yet monitor
55
+  for changes to etcd.
56
+

+ 23
- 0
fetcher.py View File

@@ -0,0 +1,23 @@
1
+#!/usr/bin/env python3
2
+
3
+import etcd
4
+
5
+
6
+class Fetcher:
7
+
8
+  def __init__(self, host, port, prefix):
9
+    self._client = etcd.Client(host=host, port=port)
10
+    self._prefix = prefix
11
+
12
+
13
+  def _read_recursive(self, key):
14
+    try:
15
+      return self._client.read(self._prefix + key, recursive=True)
16
+    except etcd.EtcdKeyNotFound:
17
+      return None 
18
+
19
+
20
+  def get_label(self, label):
21
+    node = self._read_recursive('/labels/%s' % label)
22
+    return {child.key.split('/')[-1]: child.value for child in node.children}
23
+

+ 34
- 0
generate.py View File

@@ -0,0 +1,34 @@
1
+#!/usr/bin/env python3
2
+
3
+from collections import defaultdict
4
+from fetcher import Fetcher
5
+import argparse
6
+import os
7
+
8
+parser = argparse.ArgumentParser()
9
+parser.add_argument('--name', help='Name of the docker host to request certificates for', default='unknown')
10
+parser.add_argument('--etcd-port', type=int, help='Port to connect to etcd on', default=2379)
11
+parser.add_argument('--etcd-host', help='Host to connect to etcd on', default='etcd')
12
+parser.add_argument('--etcd-prefix', help='Prefix to use when retrieving keys from etcd', default='/docker')
13
+args = parser.parse_args()
14
+
15
+domains = defaultdict(set)
16
+fetcher = Fetcher(args.etcd_host, args.etcd_port, args.etcd_prefix)
17
+for container, values in fetcher.get_label('com.chameth.vhost').items():
18
+  parts = values.split(',')
19
+  domains[parts[0].strip()] |= set([] if len(parts) == 1 else parts[1:])
20
+
21
+with open('/letsencrypt/domains.txt.new', 'w') as f:
22
+  for domain, alts in domains.items():
23
+    f.write(domain)
24
+    if len(alts):
25
+      f.write(' ' + ' ' .join(alts))
26
+    f.write('\n')
27
+
28
+try:
29
+  os.remove('/letsencrypt/domains.txt')
30
+except OSError:
31
+  pass
32
+
33
+os.rename('/letsencrypt/domains.txt.new', '/letsencrypt/domains.txt')
34
+

Loading…
Cancel
Save