initial code
This commit is contained in:
parent
53b95fad2c
commit
1db7c29c2f
6 changed files with 285 additions and 0 deletions
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## 1.0.0 - 2022-10-09
|
||||||
|
|
||||||
|
This is the initial release. This will work with Traefik IngressRoutes and create Certificates from them.
|
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1 \
|
||||||
|
ISSUER_NAME=letsencrypt \
|
||||||
|
ISSUER_KIND=ClusterIssuer \
|
||||||
|
CERT_CLEANUP=false
|
||||||
|
|
||||||
|
RUN pip install kubernetes
|
||||||
|
COPY main.py /
|
||||||
|
CMD python /main.py
|
77
README.md
Normal file
77
README.md
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
This will create a certificate request for IngressRoute objects for Traefik.
|
||||||
|
|
||||||
|
# Installing Cert-Manager and Traefik
|
||||||
|
|
||||||
|
The default values assume you have cert-manager installed, see also [cert-manager installation](https://cert-manager.io/docs/installation/helm/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install \
|
||||||
|
cert-manager jetstack/cert-manager \
|
||||||
|
--namespace cert-manager \
|
||||||
|
--create-namespace \
|
||||||
|
--version v1.9.1 \
|
||||||
|
--set installCRDs=true
|
||||||
|
```
|
||||||
|
|
||||||
|
As well as Traefik, see also [traefik installation](https://doc.traefik.io/traefik/getting-started/install-traefik/#use-the-helm-chart):
|
||||||
|
|
||||||
|
```
|
||||||
|
helm install \
|
||||||
|
traefik traefik/traefik \
|
||||||
|
--namespace cert-manager \
|
||||||
|
--create-namespace \
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding ClusterIssuer to Cert-Manager
|
||||||
|
|
||||||
|
Next you install the ClusterIssuer using `kubectl apply`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
email: manager@example.com
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: lets-encrypt
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: ""
|
||||||
|
```
|
||||||
|
|
||||||
|
# Installing Traefik to Cert-Manager
|
||||||
|
|
||||||
|
Finally you can install the traefik-certmanager.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -f traefik-certmanager.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a deployment, service account and role that can read/watch IngressRoutes and can add/delete Certficates. When starting it will check all existing IngressRoutes and see if there is a certificate for them (only for those that have a secretName). Next it will watch the addition and/or deleting of IngressRoutes. If an IngressRoute is removed, it can (false by default) remove the certificate as well.
|
||||||
|
|
||||||
|
This is an example of a IngressRoute that will be picked up by this deployment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: traefik-dashboard
|
||||||
|
namespace: traefik
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`traefik.example.com`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: api@internal
|
||||||
|
kind: TraefikService
|
||||||
|
tls:
|
||||||
|
secretName: trafik.example
|
||||||
|
```
|
||||||
|
|
129
main.py
Normal file
129
main.py
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
# helm install \
|
||||||
|
# cert-manager jetstack/cert-manager \
|
||||||
|
# --namespace cert-manager \
|
||||||
|
# --create-namespace \
|
||||||
|
# --version v1.9.1 \
|
||||||
|
# --set installCRDs=true
|
||||||
|
|
||||||
|
from unicodedata import name
|
||||||
|
from kubernetes import client, config, watch
|
||||||
|
from kubernetes.client.rest import ApiException
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
TRAEFIK_GROUP = "traefik.containo.us"
|
||||||
|
TRAEFIK_VERSION = "v1alpha1"
|
||||||
|
TRAEFIK_PLURAL = "ingressroutes"
|
||||||
|
|
||||||
|
CERT_GROUP = "cert-manager.io"
|
||||||
|
CERT_VERSION = "v1"
|
||||||
|
CERT_KIND = "Certificate"
|
||||||
|
CERT_PLURAL = "certificates"
|
||||||
|
CERT_ISSUER_NAME = os.getenv("ISSUER_NAME", "letsencrypt")
|
||||||
|
CERT_ISSUER_KIND = os.getenv("ISSUER_KIND", "ClusterIssuer")
|
||||||
|
CERT_CLEANUP = os.getenv("CERT_CLEANUP", "false").lower() in ("yes", "true", "t", "1")
|
||||||
|
|
||||||
|
def safe_get(obj, keys, default=None):
|
||||||
|
"""
|
||||||
|
Get a value from the give dict. The key is in json format, i.e. seperated by a period.
|
||||||
|
"""
|
||||||
|
v = obj
|
||||||
|
for k in keys.split("."):
|
||||||
|
if k not in v:
|
||||||
|
return default
|
||||||
|
v = v[k]
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def create_certificate(crds, namespace, secretname, routes):
|
||||||
|
"""
|
||||||
|
Create a certificate request for certmanager based on the IngressRoute
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
secret = crds.get_namespaced_custom_object(CERT_GROUP, CERT_VERSION, namespace, CERT_PLURAL, secretname)
|
||||||
|
print(f"{secretname} : certificate already exists.")
|
||||||
|
return
|
||||||
|
except ApiException as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for route in routes:
|
||||||
|
if route.get("kind") == "Rule" and "Host" in route.get("match"):
|
||||||
|
hostmatch = re.findall("Host\(([^\)]*)\)", route["match"])
|
||||||
|
hosts = re.findall('`([^`]*?)`', ",".join(hostmatch))
|
||||||
|
|
||||||
|
print(f"{secretname} : requesting a new certificate for {', '.join(hosts)}")
|
||||||
|
body = {
|
||||||
|
"apiVersion": f"{CERT_GROUP}/{CERT_VERSION}",
|
||||||
|
"kind": CERT_KIND,
|
||||||
|
"metadata": {
|
||||||
|
"name": secretname
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"dnsNames": hosts,
|
||||||
|
"secretName": secretname,
|
||||||
|
"issuerRef": {
|
||||||
|
"name": CERT_ISSUER_NAME,
|
||||||
|
"kind": CERT_ISSUER_KIND
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
crds.create_namespaced_custom_object(CERT_GROUP, CERT_VERSION, namespace, CERT_PLURAL, body)
|
||||||
|
except ApiException as e:
|
||||||
|
print("Exception when calling CustomObjectsApi->create_namespaced_custom_object: %s\n" % e)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_certificate(crds, namespace, secretname):
|
||||||
|
"""
|
||||||
|
Delete a certificate request for certmanager based on the IngressRoute.
|
||||||
|
"""
|
||||||
|
if CERT_CLEANUP:
|
||||||
|
print(f"{secretname} : removing certificate")
|
||||||
|
try:
|
||||||
|
crds.delete_namespaced_custom_object(CERT_GROUP, CERT_VERSION, namespace, CERT_PLURAL, secretname)
|
||||||
|
except ApiException as e:
|
||||||
|
print("Exception when calling CustomObjectsApi->delete_namespaced_custom_object: %s\n" % e)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Watch Traefik IngressRoute CRD and create/delete certificates based on them
|
||||||
|
"""
|
||||||
|
#config.load_kube_config()
|
||||||
|
config.load_incluster_config()
|
||||||
|
crds = client.CustomObjectsApi()
|
||||||
|
resource_version = ""
|
||||||
|
|
||||||
|
while True:
|
||||||
|
stream = watch.Watch().stream(crds.list_cluster_custom_object,
|
||||||
|
TRAEFIK_GROUP, TRAEFIK_VERSION, TRAEFIK_PLURAL,
|
||||||
|
resource_version=resource_version)
|
||||||
|
for event in stream:
|
||||||
|
t = event["type"]
|
||||||
|
obj = event["object"]
|
||||||
|
|
||||||
|
# Configure where to resume streaming.
|
||||||
|
resource_version = safe_get(obj, "metadata.resourceVersion", resource_version)
|
||||||
|
|
||||||
|
# get information about IngressRoute
|
||||||
|
namespace = safe_get(obj, "metadata.namespace")
|
||||||
|
secretname = safe_get(obj, "spec.tls.secretName")
|
||||||
|
routes = safe_get(obj, 'spec.routes')
|
||||||
|
|
||||||
|
# create a Certificate if needed
|
||||||
|
if secretname:
|
||||||
|
if t == 'ADDED':
|
||||||
|
create_certificate(crds, namespace, secretname, routes)
|
||||||
|
|
||||||
|
elif t == 'DELETED':
|
||||||
|
delete_certificate(crds, namespace, secretname)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(t)
|
||||||
|
print(json.dumps(obj, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
kubernetes
|
58
traefik-certmanager.yaml
Normal file
58
traefik-certmanager.yaml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-certmanager
|
||||||
|
namespace: traefik
|
||||||
|
---
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: traefik-certmanager
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["traefik.containo.us"]
|
||||||
|
resources: ["ingressroutes"]
|
||||||
|
verbs: ["watch"]
|
||||||
|
- apiGroups: ["cert-manager.io"]
|
||||||
|
resources: ["certificates"]
|
||||||
|
verbs: ["get", "create", "delete"]
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: traefik-certmanager
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-certmanager
|
||||||
|
namespace: traefik
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
name: traefik-certmanager
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: traefik-certmanager
|
||||||
|
namespace: traefik
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik-certmanager
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: traefik-certmanager
|
||||||
|
spec:
|
||||||
|
serviceAccount: traefik-certmanager
|
||||||
|
containers:
|
||||||
|
- name: traefik-certmanager
|
||||||
|
image: kooper/traefik-certmanager
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: ISSUER_NAME
|
||||||
|
value: letsencrypt
|
||||||
|
- name: ISSUER_KIND
|
||||||
|
value: ClusterIssuer
|
||||||
|
- name: CERT_CLEANUP
|
||||||
|
value: "false"
|
Loading…
Reference in a new issue