├── Dockerfile ├── LICENSE ├── Makefile ├── Readme.md ├── example ├── rc.example.yml ├── run.sh └── service.example.yml ├── rc.example.yml └── scripts ├── entrypoint.sh └── get-cert.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/letsencrypt/letsencrypt 2 | 3 | MAINTAINER Eli Mallon 4 | 5 | ENV KUBE_LATEST_VERSION="v1.4.6" 6 | ENV KUBE_URL="https://github.com/kubernetes/kubernetes/releases/download/${KUBE_LATEST_VERSION}/kubernetes.tar.gz" 7 | 8 | # No curl or wget -- what the hey, let's just download kubernetes with a python one-liner. 9 | RUN cd /usr/bin && \ 10 | python -c "from urllib import urlretrieve; urlretrieve('$KUBE_URL', 'kubernetes-latest.tar.gz')" && \ 11 | tar xzf kubernetes-latest.tar.gz && \ 12 | mv kubernetes/platforms/linux/amd64/kubectl ./kubectl && \ 13 | rm -rf ./kubernetes-latest.tar.gz ./kubernetes && \ 14 | mkdir /webroot 15 | 16 | WORKDIR /app 17 | ADD scripts/get-cert.sh /app/get-cert.sh 18 | ADD scripts/entrypoint.sh /app/entrypoint.sh 19 | 20 | # Start up the HTTP server at our webroot. 21 | ENTRYPOINT ["/app/entrypoint.sh"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Eli Mallon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: . 3 | docker build -t iameli/kubernetes-letsencrypt . 4 | 5 | push: 6 | docker push iameli/kubernetes-letsencrypt 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # deprecated 3 | 4 | I've moved over to [kube-lego](https://github.com/jetstack/kube-lego) for my cert retrieval, so I guess that means this project is unmaintained. They're integrated with a couple ingress controllers and autogenerate certs based on ingresses and other cool stuff like that. 5 | 6 | This project is still maybe useful if you're on a quest to string some bash scripts together and get TLS certs for whatever reason. 7 | 8 | # kubernetes-letsencrypt 9 | 10 | This project aims to be a painless way to get letsencrypt SSL certificates into your Kubernetes cluster. 11 | 12 | ## Usage 13 | 14 | 1. Create a letsencrypt ReplicationController and service. You can customize the ones provided in the "example" 15 | folder. The environment variables in the ReplicationController will determine the user parameters of your SSL 16 | certificate. 17 | 18 | 1. Configure your load balancer so that HTTP requests to the directory `/.well-known` go to the `letsencrypt` service. 19 | This process will vary depending on your cluster's load balancer. 20 | 21 | ``` 22 | server { 23 | listen 80; 24 | location /.well-known { 25 | proxy_pass http://letsencrypt.default.svc.cluster.local; 26 | } 27 | } 28 | ``` 29 | 1. Customize `example/run.sh` with the list of domains for which you'd like to generate a certificate. Now you're 30 | ready to start generating certificates. 31 | 32 | 1. Execute your `run.sh` file. It will run the command to generate the certificates in the appropriate pod, and save 33 | the certificates into a secret called `letsencrypt-ssl`. 34 | 35 | 1. Configure your load balancer pod to mount those newly-generated secrets. Your ReplicationController might look 36 | something like this: 37 | 38 | ``` 39 | apiVersion: v1 40 | kind: ReplicationController 41 | metadata: 42 | name: load-balancer 43 | spec: 44 | replicas: 1 45 | selector: 46 | app: load-balancer 47 | template: 48 | metadata: 49 | labels: 50 | app: load-balancer 51 | spec: 52 | volumes: 53 | - name: ssl 54 | secret: 55 | secretName: letsencrypt-ssl 56 | containers: 57 | - name: "load-balancer" 58 | image: "your-user/your-nginx" 59 | imagePullPolicy: Always 60 | volumeMounts: 61 | - name: ssl 62 | mountPath: /keys 63 | readOnly: true 64 | ``` 65 | 66 | 1. Configure your load balancer to use those newly-mounted certificates. An nginx config might look something like 67 | this: 68 | 69 | ``` 70 | ssl_certificate /keys/certchain.pem; 71 | ssl_certificate_key /keys/key.pem; 72 | ``` 73 | 74 | 1. You're done! You should probably set up something somewhere to regenerate your certificates monthly or so. 75 | 76 | ## Secret format 77 | 78 | When kubernetes-letsencrypt generates a key and certificate, it saves it in a secret. By default, this secret is named 79 | `letsencrypt-ssl`. This secret contains four files: 80 | 81 | * `key.pem` - Contains the newly generated secret key. 82 | * `cert.pem` - Contains the newly generated certificate, signed by Let's Encrypt. (This is what Apache uses.) 83 | * `chain.pem` - Contains the certificate vendor chain necessary to validate the certificate. 84 | * `certchain.pem` - Concatins a concatenation of cert.pem and chain.pem. (This is what nginx uses.) 85 | -------------------------------------------------------------------------------- /example/rc.example.yml: -------------------------------------------------------------------------------- 1 | 2 | # This is an example of a replication controller you'd run. 3 | apiVersion: v1 4 | kind: ReplicationController 5 | metadata: 6 | name: letsencrypt 7 | namespace: default 8 | spec: 9 | replicas: 1 10 | selector: 11 | app: letsencrypt 12 | template: 13 | metadata: 14 | name: letsencrypt 15 | labels: 16 | app: letsencrypt 17 | spec: 18 | containers: 19 | - name: letsencrypt 20 | image: docker.io/iameli/kubernetes-letsencrypt 21 | imagePullPolicy: Always 22 | env: 23 | - name: tos 24 | value: "true" 25 | - name: country 26 | value: US 27 | - name: state 28 | value: Washington 29 | - name: town 30 | value: Seattle 31 | - name: organization 32 | value: Dandiprat Industries 33 | - name: email 34 | value: eli@streamprov.com 35 | - name: secretName 36 | value: letsencrypt-ssl 37 | - name: acmeServer 38 | value: https://acme-staging.api.letsencrypt.org/directory 39 | - name: namespace 40 | value: default 41 | -------------------------------------------------------------------------------- /example/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example script to generate your certs automatically. 4 | podName=$(kubectl get pods -l 'app=letsencrypt' -o name | sed 's/pod\///') 5 | kubectl exec -it "$podName" /app/get-cert.sh \ 6 | example.com \ 7 | www.example.com \ 8 | api.example.com 9 | 10 | -------------------------------------------------------------------------------- /example/service.example.yml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: letsencrypt 6 | namespace: default 7 | spec: 8 | type: ClusterIP 9 | selector: 10 | app: letsencrypt 11 | ports: 12 | - name: http 13 | protocol: TCP 14 | port: 80 15 | targetPort: 80 16 | -------------------------------------------------------------------------------- /rc.example.yml: -------------------------------------------------------------------------------- 1 | 2 | # This is an example of a replication controller you'd run. 3 | apiVersion: v1 4 | kind: ReplicationController 5 | metadata: 6 | name: letsencrypt 7 | namespace: default 8 | spec: 9 | replicas: 1 10 | selector: 11 | app: letsencrypt 12 | template: 13 | metadata: 14 | name: letsencrypt 15 | labels: 16 | app: letsencrypt 17 | spec: 18 | containers: 19 | - name: letsencrypt 20 | image: docker.io/iameli/kubernetes-letsencrypt 21 | imagePullPolicy: Always 22 | env: 23 | - name: tos 24 | value: "true" 25 | - name: country 26 | value: US 27 | - name: state 28 | value: Washington 29 | - name: town 30 | value: Seattle 31 | - name: organization 32 | value: Dandiprat Industries 33 | - name: email 34 | value: eli@stream.kitchen 35 | - name: secretName 36 | value: letsencrypt-ssl 37 | - name: acmeServer 38 | value: https://acme-staging.api.letsencrypt.org/directory 39 | - name: namespace 40 | value: default 41 | -------------------------------------------------------------------------------- /scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /webroot 3 | python -m SimpleHTTPServer 80 4 | -------------------------------------------------------------------------------- /scripts/get-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Adapted from Thomas Pircher's Let's Encrypt Manual Mode script: 4 | # https://www.tty1.net/blog/2015/using-letsencrypt-in-manual-mode_en.html 5 | # Redistributed under a Creative Commons Attribution-Share Alike 3.0 Unported License 6 | # 7 | # To use Let's Encrypt, you must read the TOS at https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf and 8 | # acknowledge your agreement by setting the "tos" environment variable to "true" 9 | # 10 | # This thing has lots of environment variables: 11 | # Required: 12 | # tos 13 | # country 14 | # state 15 | # town 16 | # organization 17 | # email 18 | # secretName 19 | # Optional: 20 | # acmeServer - Set to https://acme-staging.api.letsencrypt.org/directory if you'd like to generate a testing cert 21 | # namespace - Default "default" 22 | 23 | set -o errexit 24 | set -o nounset 25 | set -o pipefail 26 | 27 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 28 | 29 | # Set up optional parameters 30 | acmeServer="${acmeServer:-https://acme-v01.api.letsencrypt.org/directory}" 31 | namespace="${namespace:-default}" 32 | 33 | # Constants 34 | key="key.pem" 35 | csr="signreq.der" 36 | newSecretFile="newsecret.yml" 37 | rawDomains=$* 38 | 39 | function log() { 40 | echo "" 41 | echo "=================================================" 42 | echo "kubernetes-letsencrypt: $1" 43 | echo "=================================================" 44 | echo "" 45 | } 46 | 47 | function encodeFile() { 48 | cat "$1" | base64 --wrap=0 49 | } 50 | 51 | if [ "${tos:-}" != "true" ] ; then 52 | echo 'Error: you must agree to the terms of service and set tos="true"' 53 | exit 1 54 | fi 55 | 56 | if [ $# -lt 1 ]; then 57 | echo "$0: error: at least one domain name required." 58 | exit 1 59 | fi 60 | domain=$1 61 | 62 | shift 63 | other_domains= 64 | while [ $# -gt 0 ]; do 65 | other_domains="$other_domains,DNS:$1" 66 | shift 67 | done 68 | 69 | tmpdir= 70 | cleanup() { 71 | if [ -n "$tmpdir" -a -d "$tmpdir" ]; then 72 | rm -rf "$tmpdir" 73 | fi 74 | } 75 | trap cleanup INT QUIT TERM EXIT 76 | tmpdir=`mktemp -d -t mkcert-XXXXXXX` 77 | 78 | sslcnf="$tmpdir/openssl.cnf" 79 | cat /etc/ssl/openssl.cnf > "$sslcnf" 80 | echo "[SAN]" >> "$sslcnf" 81 | echo "subjectAltName=DNS:$domain$other_domains" >> "$sslcnf" 82 | 83 | # You can only request so many certs from letsencrypt before they stop giving them to you. As such, let's do a quick 84 | # Kubernetes sanity check before we go talk to them -- that way if this pod's configuration is broken we won't use up 85 | # one of our cert generations. 86 | log "Doing Kubernetes sanity check" 87 | kubectl --namespace="$namespace" get pods > /dev/null || ( 88 | log "Error!" 89 | echo "Unable to communicate with Kubernetes. Check to make sure you are in a cluster and your serviceaccount token" 90 | echo "is configured properly." 91 | exit 1 92 | ) 93 | 94 | log "Generating key and CSR" 95 | openssl req \ 96 | -new -newkey rsa:2048 -sha256 -nodes \ 97 | -keyout "$key" -out "$csr" -outform der \ 98 | -subj "/C=$country/ST=$state/L=$town/O=$domain/emailAddress=$email/CN=$domain" \ 99 | -reqexts SAN \ 100 | -config "$sslcnf" 101 | 102 | log "Retrieving new cert with letsencrypt" 103 | certbot certonly \ 104 | --server "$acmeServer" \ 105 | --text \ 106 | --agree-tos \ 107 | --config-dir letsencrypt/etc \ 108 | --logs-dir letsencrypt/log \ 109 | --work-dir letsencrypt/lib \ 110 | --email "$email" \ 111 | --csr "$csr" \ 112 | --authenticator webroot \ 113 | --webroot-path "/webroot" 114 | 115 | log "Generating secret" 116 | (cat << EOF 117 | apiVersion: v1 118 | kind: Secret 119 | metadata: 120 | name: "$secretName" 121 | namespace: "$namespace" 122 | type: Opaque 123 | data: 124 | key.pem: "$(encodeFile key.pem)" 125 | cert.pem: "$(encodeFile 0000_cert.pem)" 126 | chain.pem: "$(encodeFile 0000_chain.pem)" 127 | certchain.pem: "$(encodeFile 0001_chain.pem)" 128 | EOF 129 | ) > "$newSecretFile" 130 | echo "Done!" 131 | 132 | kubectl get -f "$newSecretFile" > /dev/null && ( 133 | log "Secret exists, running kubectl apply" 134 | kubectl apply -f "$newSecretFile" 135 | ) || ( 136 | log "Secret does not exist, running kubectl create" 137 | kubectl create -f "$newSecretFile" 138 | ) 139 | 140 | rm -rf $DIR/*.pem 141 | rm -rf $DIR/*.der 142 | rm -rf $DIR/*.yml 143 | 144 | log "Done!" 145 | --------------------------------------------------------------------------------