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