mirror of
https://github.com/eliasstepanik/caddy-ingess.git
synced 2026-01-11 04:28:28 +00:00
* Update to caddy v2.0.0 * Fixes from #24 * Update rbac api and move ingresses from extensions api to networking * Fix matchers * Allow default backend * Use caddyconfig.JSON * Fix issuer * Use empty image for docker
233 lines
6.1 KiB
Go
233 lines
6.1 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
apiv1 "k8s.io/api/core/v1"
|
|
"k8s.io/api/networking/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
sv1 "k8s.io/client-go/informers/core/v1"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/util/workqueue"
|
|
)
|
|
|
|
var certDir = filepath.FromSlash("/etc/caddy/certs")
|
|
|
|
// CertManager manager user defined certs on ingress resources for caddy.
|
|
type CertManager struct {
|
|
certInformer cache.Controller
|
|
certs []certificate
|
|
syncQueue workqueue.RateLimitingInterface
|
|
synced bool
|
|
}
|
|
|
|
type certificate struct {
|
|
name string
|
|
namespace string
|
|
}
|
|
|
|
// HandleOwnCertManagement handles whether we need to watch for user defined
|
|
// certs and update caddy.
|
|
func (c *CaddyController) HandleOwnCertManagement(ings []*v1beta1.Ingress) (map[string]interface{}, error) {
|
|
var certs []certificate
|
|
var hosts []string
|
|
|
|
// do we have any ingresses with TLS certificates and secrets defined on them?
|
|
for _, ing := range ings {
|
|
for _, tlsRule := range ing.Spec.TLS {
|
|
for _, h := range tlsRule.Hosts {
|
|
hosts = append(hosts, h)
|
|
}
|
|
|
|
c := certificate{name: tlsRule.SecretName, namespace: ing.Namespace}
|
|
certs = append(certs, c)
|
|
}
|
|
}
|
|
|
|
// run the caddy cert sync now (ONE TIME) but only run it in the future
|
|
// when a cert has been updated (or a new cert has been added)
|
|
if len(certs) > 0 && c.certManager == nil {
|
|
err := syncCertificates(certs, c.kubeClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
informer, err := newSecretInformer(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.certManager = &CertManager{
|
|
certs: certs,
|
|
certInformer: informer,
|
|
syncQueue: c.syncQueue,
|
|
}
|
|
|
|
// start the informer to listen to secrets
|
|
go informer.Run(c.stopChan)
|
|
}
|
|
|
|
if len(certs) > 0 {
|
|
return getTLSConfig(hosts), nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// newSecretInformer creates an informer to listen to updates to secrets.
|
|
func newSecretInformer(c *CaddyController) (cache.Controller, error) {
|
|
secretInformer := sv1.NewSecretInformer(c.kubeClient, c.config.WatchNamespace, secretSyncInterval, cache.Indexers{})
|
|
secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
AddFunc: c.onSecretResourceAdded,
|
|
UpdateFunc: c.onSecretResourceUpdated,
|
|
DeleteFunc: c.onSecretResourceDeleted,
|
|
})
|
|
|
|
return secretInformer, nil
|
|
}
|
|
|
|
// getTLSConfig returns the caddy config for certificate management to load all certs from certDir.
|
|
func getTLSConfig(hosts []string) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"load_folders": json.RawMessage(`["` + certDir + `"]`),
|
|
"hosts": hosts,
|
|
}
|
|
}
|
|
|
|
// syncCertificates downloads the certificate files defined on a ingress resource and
|
|
// stores it locally in this pod for use by caddy.
|
|
func syncCertificates(certs []certificate, kubeClient *kubernetes.Clientset) error {
|
|
logrus.Info("Found TLS certificates on ingress resource. Syncing...")
|
|
|
|
certData := make(map[string]map[string][]byte, len(certs))
|
|
for _, cert := range certs {
|
|
s, err := kubeClient.CoreV1().Secrets(cert.namespace).Get(cert.name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
certData[cert.name] = s.Data
|
|
}
|
|
|
|
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
|
err = os.MkdirAll(certDir, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// combine crt and key and combine to .pem in cert directory
|
|
for secret, data := range certData {
|
|
content := make([]byte, 0)
|
|
|
|
for _, cert := range data {
|
|
content = append(content, cert...)
|
|
}
|
|
|
|
err := ioutil.WriteFile(filepath.Join(certDir, secret+".pem"), content, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SecretResourceAddedAction provides an implementation of the action interface.
|
|
type SecretResourceAddedAction struct {
|
|
resource *apiv1.Secret
|
|
}
|
|
|
|
// SecretResourceUpdatedAction provides an implementation of the action interface.
|
|
type SecretResourceUpdatedAction struct {
|
|
resource *apiv1.Secret
|
|
oldResource *apiv1.Secret
|
|
}
|
|
|
|
// SecretResourceDeletedAction provides an implementation of the action interface.
|
|
type SecretResourceDeletedAction struct {
|
|
resource *apiv1.Secret
|
|
}
|
|
|
|
// onSecretResourceAdded runs when a secret resource is added to the cluster.
|
|
func (c *CaddyController) onSecretResourceAdded(obj interface{}) {
|
|
s, ok := obj.(*apiv1.Secret)
|
|
if ok {
|
|
for _, secret := range c.certManager.certs {
|
|
if s.Name == secret.name {
|
|
c.syncQueue.Add(SecretResourceAddedAction{
|
|
resource: s,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeFile writes a secret to a .pem file on disk.
|
|
func writeFile(s *apiv1.Secret) error {
|
|
content := make([]byte, 0)
|
|
|
|
for _, cert := range s.Data {
|
|
content = append(content, cert...)
|
|
}
|
|
|
|
err := ioutil.WriteFile(filepath.Join(certDir, s.Name+".pem"), content, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// onSecretResourceUpdated is run when a secret resource is updated in the cluster.
|
|
func (c *CaddyController) onSecretResourceUpdated(old interface{}, new interface{}) {
|
|
s, ok := old.(*apiv1.Secret)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
snew, ok := new.(*apiv1.Secret)
|
|
for _, secret := range c.certManager.certs {
|
|
if s.Name == secret.name {
|
|
c.syncQueue.Add(SecretResourceUpdatedAction{
|
|
resource: snew,
|
|
oldResource: s,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// onSecretResourceDeleted is run when a secret resource is deleted from the cluster.
|
|
func (c *CaddyController) onSecretResourceDeleted(obj interface{}) {
|
|
s, ok := obj.(*apiv1.Secret)
|
|
if ok {
|
|
for _, secret := range c.certManager.certs {
|
|
if s.Name == secret.name {
|
|
c.syncQueue.Add(SecretResourceDeletedAction{
|
|
resource: s,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle is run when a SecretResourceDeletedAction appears in the queue.
|
|
func (r SecretResourceDeletedAction) handle(c *CaddyController) error {
|
|
return os.Remove(filepath.Join(certDir, r.resource.Name+".pem"))
|
|
}
|
|
|
|
// handle is run when a SecretResourceUpdatedAction appears in the queue.
|
|
func (r SecretResourceUpdatedAction) handle(c *CaddyController) error {
|
|
return writeFile(r.resource)
|
|
}
|
|
|
|
// handle is run when a SecretResourceAddedAction appears in the queue.
|
|
func (r SecretResourceAddedAction) handle(c *CaddyController) error {
|
|
return writeFile(r.resource)
|
|
}
|