mirror of
https://github.com/eliasstepanik/caddy-ingess.git
synced 2026-01-11 12:38:27 +00:00
Implement certmagic storage interface for k8s
* Add automatic https functionality
This commit is contained in:
parent
54bd76cb78
commit
8a34e3a74b
@ -1,4 +1,8 @@
|
|||||||
|
FROM alpine:latest as certs
|
||||||
|
RUN apk --update add ca-certificates
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY ./bin/ingress-controller .
|
COPY ./bin/ingress-controller .
|
||||||
|
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
EXPOSE 80 443
|
EXPOSE 80 443
|
||||||
ENTRYPOINT ["/ingress-controller"]
|
ENTRYPOINT ["/ingress-controller"]
|
||||||
@ -2,9 +2,11 @@ apiVersion: extensions/v1beta1
|
|||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: example
|
name: example
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: caddy
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- host: hello-world.xyz
|
- host: caddy2.kubed.co
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /hello2
|
- path: /hello2
|
||||||
@ -14,4 +16,4 @@ spec:
|
|||||||
- path: /hello
|
- path: /hello
|
||||||
backend:
|
backend:
|
||||||
serviceName: example
|
serviceName: example
|
||||||
servicePort: 8080
|
servicePort: 8080
|
||||||
@ -3,7 +3,7 @@ apiVersion: v1
|
|||||||
metadata:
|
metadata:
|
||||||
name: example
|
name: example
|
||||||
spec:
|
spec:
|
||||||
type: NodePort
|
type: ClusterIP
|
||||||
selector:
|
selector:
|
||||||
app: example
|
app: example
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -3,7 +3,7 @@ apiVersion: v1
|
|||||||
metadata:
|
metadata:
|
||||||
name: example2
|
name: example2
|
||||||
spec:
|
spec:
|
||||||
type: NodePort
|
type: ClusterIP
|
||||||
selector:
|
selector:
|
||||||
app: example2
|
app: example2
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -3,22 +3,21 @@ package caddy
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"bitbucket.org/lightcodelabs/caddy2"
|
"bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverRoute struct {
|
type serverRoute struct {
|
||||||
Matchers map[string]json.RawMessage `json:"match"`
|
Matchers map[string]json.RawMessage `json:"match"`
|
||||||
Apply []map[string]string `json:"apply"`
|
Apply []map[string]string `json:"apply"`
|
||||||
Respond proxyConfig `json:"respond"`
|
Respond proxyConfig `json:"respond"`
|
||||||
Exclusive bool `json:"exclusive"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type routeList []serverRoute
|
type routeList []serverRoute
|
||||||
|
|
||||||
type proxyConfig struct {
|
type proxyConfig struct {
|
||||||
Module string `json:"_module"`
|
Module string `json:"responder"`
|
||||||
LoadBalanceType string `json:"load_balance_type"`
|
LoadBalanceType string `json:"load_balance_type"`
|
||||||
Upstreams []upstreamConfig
|
Upstreams []upstreamConfig `json:"upstreams"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type upstreamConfig struct {
|
type upstreamConfig struct {
|
||||||
@ -26,12 +25,13 @@ type upstreamConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type httpServerConfig struct {
|
type httpServerConfig struct {
|
||||||
Listen []string `json:"listen"`
|
Listen []string `json:"listen"`
|
||||||
ReadTimeout caddy2.Duration `json:"read_timeout"`
|
ReadTimeout string `json:"read_timeout"`
|
||||||
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
|
DisableAutoHTTPS bool `json:"disable_auto_https"`
|
||||||
HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
|
// ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
|
||||||
Routes routeList `json:"routes"`
|
// HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
|
||||||
Errors httpErrorConfig `json:"errors"`
|
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
|
||||||
|
Routes routeList `json:"routes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpErrorConfig struct {
|
type httpErrorConfig struct {
|
||||||
@ -46,23 +46,66 @@ type servers struct {
|
|||||||
Servers serverConfig `json:"servers"`
|
Servers serverConfig `json:"servers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
Module string `json:"module"`
|
||||||
|
Automation caddytls.AutomationConfig `json:"automation"`
|
||||||
|
}
|
||||||
|
|
||||||
type httpServer struct {
|
type httpServer struct {
|
||||||
HTTP servers `json:"http"`
|
TLS TLSConfig `json:"tls"`
|
||||||
|
HTTP servers `json:"http"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageValues represents the config for certmagic storage providers.
|
||||||
|
type StorageValues struct {
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage represents the certmagic storage configuration.
|
||||||
|
type Storage struct {
|
||||||
|
System string `json:"system"`
|
||||||
|
StorageValues
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config represents a caddy2 config file.
|
// Config represents a caddy2 config file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Modules httpServer `json:"modules"`
|
Storage Storage `json:"storage"`
|
||||||
|
Modules httpServer `json:"apps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a plain slate caddy2 config file.
|
// NewConfig returns a plain slate caddy2 config file.
|
||||||
func NewConfig() *Config {
|
func NewConfig(namespace string) *Config {
|
||||||
|
// TODO :- get email from arguments to ingress controller
|
||||||
|
autoPolicyBytes := json.RawMessage(`{"module": "acme", "email": "navdgo@gmail.com"}`)
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
|
Storage: Storage{
|
||||||
|
System: "secret_store",
|
||||||
|
StorageValues: StorageValues{
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
Modules: httpServer{
|
Modules: httpServer{
|
||||||
|
TLS: TLSConfig{
|
||||||
|
Module: "acme",
|
||||||
|
Automation: caddytls.AutomationConfig{
|
||||||
|
Policies: []caddytls.AutomationPolicy{
|
||||||
|
caddytls.AutomationPolicy{
|
||||||
|
Hosts: nil,
|
||||||
|
Management: autoPolicyBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
HTTP: servers{
|
HTTP: servers{
|
||||||
Servers: serverConfig{
|
Servers: serverConfig{
|
||||||
Server: httpServerConfig{
|
Server: httpServerConfig{
|
||||||
Listen: []string{":80", ":443"},
|
DisableAutoHTTPS: false, // TODO :- allow to be set from arguments to ingress controller
|
||||||
|
ReadTimeout: "30s",
|
||||||
|
Listen: []string{":80", ":443"},
|
||||||
|
TLSConnPolicies: caddytls.ConnectionPolicies{
|
||||||
|
&caddytls.ConnectionPolicy{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,19 +7,24 @@ import (
|
|||||||
"k8s.io/api/extensions/v1beta1"
|
"k8s.io/api/extensions/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ~~~~
|
|
||||||
// TODO :-
|
|
||||||
// when setting the upstream url we should should bypass kube-proxy and get the ip address of
|
|
||||||
// the pod for the deployment we are proxying to so that we can proxy to that ip address port.
|
|
||||||
// this is good for session affinity and increases performance (since we don't have to hit dns).
|
|
||||||
// ~~~~
|
|
||||||
|
|
||||||
// ConvertToCaddyConfig returns a new caddy routelist based off of ingresses managed by this controller.
|
// ConvertToCaddyConfig returns a new caddy routelist based off of ingresses managed by this controller.
|
||||||
func ConvertToCaddyConfig(ings []*v1beta1.Ingress) ([]serverRoute, error) {
|
func ConvertToCaddyConfig(ings []*v1beta1.Ingress) ([]serverRoute, []string, error) {
|
||||||
|
// ~~~~
|
||||||
|
// TODO :-
|
||||||
|
// when setting the upstream url we should should bypass kube-proxy and get the ip address of
|
||||||
|
// the pod for the deployment we are proxying to so that we can proxy to that ip address port.
|
||||||
|
// this is good for session affinity and increases performance (since we don't have to hit dns).
|
||||||
|
// ~~~~
|
||||||
|
|
||||||
|
// record hosts for tls policies
|
||||||
|
var hosts []string
|
||||||
|
|
||||||
// create a server route for each ingress route
|
// create a server route for each ingress route
|
||||||
var routes routeList
|
var routes routeList
|
||||||
for _, ing := range ings {
|
for _, ing := range ings {
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
|
hosts = append(hosts, rule.Host)
|
||||||
|
|
||||||
for _, path := range rule.HTTP.Paths {
|
for _, path := range rule.HTTP.Paths {
|
||||||
r := baseRoute(path.Backend.ServiceName)
|
r := baseRoute(path.Backend.ServiceName)
|
||||||
|
|
||||||
@ -32,20 +37,28 @@ func ConvertToCaddyConfig(ings []*v1beta1.Ingress) ([]serverRoute, error) {
|
|||||||
"path": p,
|
"path": p,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add logging middleware to all routes
|
||||||
|
r.Apply = []map[string]string{
|
||||||
|
map[string]string{
|
||||||
|
"file": "access.log",
|
||||||
|
"middleware": "log",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
routes = append(routes, r)
|
routes = append(routes, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes, nil
|
return routes, hosts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func baseRoute(upstream string) serverRoute {
|
func baseRoute(upstream string) serverRoute {
|
||||||
return serverRoute{
|
return serverRoute{
|
||||||
Apply: []map[string]string{
|
Apply: []map[string]string{
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"_module": "log",
|
"middleware": "log",
|
||||||
"file": "access.log",
|
"file": "access.log",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Respond: proxyConfig{
|
Respond: proxyConfig{
|
||||||
|
|||||||
@ -74,6 +74,9 @@ func (r ResourceAddedAction) handle(c *CaddyController) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// js, _ := json.MarshalIndent(c.resourceStore.CaddyConfig, "", " ")
|
||||||
|
// fmt.Printf("\n%v\n", string(js))
|
||||||
|
|
||||||
// ensure that ingress source is updated to point to this ingress controller's ip
|
// ensure that ingress source is updated to point to this ingress controller's ip
|
||||||
err = c.syncStatus([]*v1beta1.Ingress{ing})
|
err = c.syncStatus([]*v1beta1.Ingress{ing})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,13 +132,19 @@ func (r ResourceDeletedAction) handle(c *CaddyController) error {
|
|||||||
|
|
||||||
func updateConfig(c *CaddyController) error {
|
func updateConfig(c *CaddyController) error {
|
||||||
// update internal caddy config with new ingress info
|
// update internal caddy config with new ingress info
|
||||||
serverRoutes, err := caddy.ConvertToCaddyConfig(c.resourceStore.Ingresses)
|
serverRoutes, hosts, err := caddy.ConvertToCaddyConfig(c.resourceStore.Ingresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "converting ingress resources to caddy config")
|
return errors.Wrap(err, "converting ingress resources to caddy config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.resourceStore.CaddyConfig != nil {
|
if c.resourceStore.CaddyConfig != nil {
|
||||||
c.resourceStore.CaddyConfig.Modules.HTTP.Servers.Server.Routes = serverRoutes
|
c.resourceStore.CaddyConfig.Modules.HTTP.Servers.Server.Routes = serverRoutes
|
||||||
|
|
||||||
|
// set tls policies
|
||||||
|
p := c.resourceStore.CaddyConfig.Modules.TLS.Automation.Policies
|
||||||
|
for i := range p {
|
||||||
|
p[i].Hosts = hosts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload caddy2 config with newConfig
|
// reload caddy2 config with newConfig
|
||||||
|
|||||||
@ -3,7 +3,9 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -26,6 +28,8 @@ import (
|
|||||||
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
|
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
|
||||||
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
|
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
|
||||||
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
|
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
|
||||||
|
"bitbucket.org/lightcodelabs/ingress/pkg/storage"
|
||||||
|
_ "bitbucket.org/lightcodelabs/ingress/pkg/storage"
|
||||||
_ "bitbucket.org/lightcodelabs/proxy"
|
_ "bitbucket.org/lightcodelabs/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -53,6 +57,12 @@ type CaddyController struct {
|
|||||||
|
|
||||||
// NewCaddyController returns an instance of the caddy ingress controller.
|
// NewCaddyController returns an instance of the caddy ingress controller.
|
||||||
func NewCaddyController(namespace string, kubeClient *kubernetes.Clientset, resource string, restClient rest.Interface) *CaddyController {
|
func NewCaddyController(namespace string, kubeClient *kubernetes.Clientset, resource string, restClient rest.Interface) *CaddyController {
|
||||||
|
// TODO :- we should get the namespace of the ingress we are processing to store secrets
|
||||||
|
// Do this in the SecretStorage package
|
||||||
|
if namespace == "" {
|
||||||
|
namespace = "default"
|
||||||
|
}
|
||||||
|
|
||||||
controller := &CaddyController{
|
controller := &CaddyController{
|
||||||
kubeClient: kubeClient,
|
kubeClient: kubeClient,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
@ -69,7 +79,7 @@ func NewCaddyController(namespace string, kubeClient *kubernetes.Clientset, reso
|
|||||||
|
|
||||||
controller.indexer = indexer
|
controller.indexer = indexer
|
||||||
controller.informer = informer
|
controller.informer = informer
|
||||||
controller.resourceStore = store.NewStore(controller.kubeClient)
|
controller.resourceStore = store.NewStore(controller.kubeClient, namespace)
|
||||||
|
|
||||||
podInfo, err := pod.GetPodDetails(kubeClient)
|
podInfo, err := pod.GetPodDetails(kubeClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -80,6 +90,24 @@ func NewCaddyController(namespace string, kubeClient *kubernetes.Clientset, reso
|
|||||||
// attempt to do initial sync with ingresses
|
// attempt to do initial sync with ingresses
|
||||||
controller.syncQueue.Add(SyncStatusAction{})
|
controller.syncQueue.Add(SyncStatusAction{})
|
||||||
|
|
||||||
|
// Register caddy cert storage module.
|
||||||
|
caddy2.RegisterModule(caddy2.Module{
|
||||||
|
Name: "caddy.storage.secret_store",
|
||||||
|
New: func() (interface{}, error) {
|
||||||
|
ss := &storage.SecretStorage{
|
||||||
|
Namespace: namespace,
|
||||||
|
KubeClient: kubeClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
err = caddy2.StartAdmin("127.0.0.1:1234")
|
||||||
|
if err != nil {
|
||||||
|
klog.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,12 +129,16 @@ func (c *CaddyController) reloadCaddy() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgReader := bytes.NewReader(j)
|
// post to load endpoint
|
||||||
err = caddy2.Load(cfgReader)
|
resp, err := http.Post("http://127.0.0.1:1234/load", "application/json", bytes.NewBuffer(j))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New("could not reload caddy config")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ type Store struct {
|
|||||||
|
|
||||||
// NewStore returns a new store that keeps track of ingresses and secrets. It will attempt to get
|
// NewStore returns a new store that keeps track of ingresses and secrets. It will attempt to get
|
||||||
// all current ingresses before returning.
|
// all current ingresses before returning.
|
||||||
func NewStore(kubeClient *kubernetes.Clientset) *Store {
|
func NewStore(kubeClient *kubernetes.Clientset, namespace string) *Store {
|
||||||
ingresses, err := kubeClient.ExtensionsV1beta1().Ingresses("").List(v1.ListOptions{})
|
ingresses, err := kubeClient.ExtensionsV1beta1().Ingresses("").List(v1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("could not get existing ingresses in cluster")
|
klog.Errorf("could not get existing ingresses in cluster")
|
||||||
@ -26,7 +26,7 @@ func NewStore(kubeClient *kubernetes.Clientset) *Store {
|
|||||||
|
|
||||||
s := &Store{
|
s := &Store{
|
||||||
Ingresses: []*v1beta1.Ingress{},
|
Ingresses: []*v1beta1.Ingress{},
|
||||||
CaddyConfig: caddy.NewConfig(),
|
CaddyConfig: caddy.NewConfig(namespace),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, i := range ingresses.Items {
|
for _, i := range ingresses.Items {
|
||||||
|
|||||||
@ -1,30 +1,25 @@
|
|||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: caddyingresscontroller-role
|
name: caddy-ingress-controller-role
|
||||||
rules:
|
rules:
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
- "extensions"
|
- "extensions"
|
||||||
resources:
|
resources:
|
||||||
- ingresses
|
- ingresses
|
||||||
- routes
|
|
||||||
- extensions
|
|
||||||
- ingresses/status
|
- ingresses/status
|
||||||
verbs:
|
- secrets
|
||||||
- list
|
verbs: ["*"]
|
||||||
- get
|
|
||||||
- update
|
|
||||||
- patch
|
|
||||||
- watch
|
|
||||||
- delete
|
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- pods
|
- pods
|
||||||
- nodes
|
- nodes
|
||||||
|
- routes
|
||||||
|
- extensions
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- get
|
- get
|
||||||
- watch
|
- watch
|
||||||
@ -1,12 +1,12 @@
|
|||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||||
metadata:
|
metadata:
|
||||||
name: caddyingresscontroller-role-binding
|
name: caddy-ingress-controller-role-binding
|
||||||
roleRef:
|
roleRef:
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
name: caddyingresscontroller-role
|
name: caddy-ingress-controller-role
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
subjects:
|
subjects:
|
||||||
- kind: ServiceAccount
|
- kind: ServiceAccount
|
||||||
name: caddyingresscontroller
|
name: caddy-ingress-controller
|
||||||
namespace: default
|
namespace: default
|
||||||
@ -1,10 +1,10 @@
|
|||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: caddyingresscontroller
|
name: caddy-ingress-controller
|
||||||
labels:
|
labels:
|
||||||
app: caddyIngressController
|
app: caddy-ingress-controller
|
||||||
chart: "caddyingresscontroller-v0.1.0"
|
chart: "caddy-ingress-controller-v0.1.0"
|
||||||
release: "release-name"
|
release: "release-name"
|
||||||
heritage: "Tiller"
|
heritage: "Tiller"
|
||||||
version: v0.1.0
|
version: v0.1.0
|
||||||
@ -14,21 +14,22 @@ spec:
|
|||||||
revisionHistoryLimit: 2
|
revisionHistoryLimit: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: caddyIngressController
|
app: caddy-ingress-controller
|
||||||
release: "release-name"
|
release: "release-name"
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: caddyIngressController
|
app: caddy-ingress-controller
|
||||||
chart: "caddyingresscontroller-v0.1.0"
|
chart: "caddy-ingress-controller-v0.1.0"
|
||||||
release: "release-name"
|
release: "release-name"
|
||||||
heritage: "Tiller"
|
heritage: "Tiller"
|
||||||
version: v0.1.0
|
version: v0.1.0
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: caddyingresscontroller
|
serviceAccountName: caddy-ingress-controller
|
||||||
containers:
|
containers:
|
||||||
- name: caddyingresscontroller
|
- name: caddy-ingress-controller
|
||||||
image: "caddy/ingresscontroller"
|
image: "gcr.io/danny-239313/ingresscontroller"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
securityContext:
|
securityContext:
|
||||||
allowPrivilegeEscalation: true
|
allowPrivilegeEscalation: true
|
||||||
@ -43,10 +44,8 @@ spec:
|
|||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 80
|
containerPort: 80
|
||||||
hostPort: 80 # optional, required if running in minikube
|
|
||||||
- name: https
|
- name: https
|
||||||
containerPort: 443
|
containerPort: 443
|
||||||
hostPort: 443 # optional, required if running in minikube
|
|
||||||
env:
|
env:
|
||||||
- name: POD_NAME
|
- name: POD_NAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
|
|||||||
19
kubernetes/generated/loadbalancer.yaml
Normal file
19
kubernetes/generated/loadbalancer.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: caddy-ingress-controller
|
||||||
|
labels:
|
||||||
|
app: caddy-ingress-controller
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: http
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: https
|
||||||
|
selector:
|
||||||
|
app: caddy-ingress-controller
|
||||||
|
type: "LoadBalancer"
|
||||||
@ -1,10 +1,11 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
metadata:
|
metadata:
|
||||||
name: caddyingresscontroller
|
|
||||||
labels:
|
labels:
|
||||||
app: caddyIngressController
|
app: caddy-ingress-controller
|
||||||
chart: "caddyingresscontroller-v0.1.0"
|
chart: "caddy-ingress-controller-v0.1.0"
|
||||||
release: "release-name"
|
release: "release-name"
|
||||||
heritage: "Tiller"
|
heritage: "Tiller"
|
||||||
version: v0.1.0
|
version: v0.1.0
|
||||||
|
|
||||||
|
name: caddy-ingress-controller
|
||||||
@ -1,4 +1,4 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
description: A helm chart for the Caddy Kubernetes ingress controller
|
description: A helm chart for the Caddy Kubernetes ingress controller
|
||||||
name: caddyingresscontroller
|
name: caddy-ingress-controller
|
||||||
version: v0.1.0
|
version: v0.1.0
|
||||||
|
|||||||
@ -9,19 +9,17 @@ rules:
|
|||||||
- "extensions"
|
- "extensions"
|
||||||
resources:
|
resources:
|
||||||
- ingresses
|
- ingresses
|
||||||
- routes
|
- ingresses/status
|
||||||
verbs:
|
- secrets
|
||||||
- list
|
verbs: ["*"]
|
||||||
- get
|
|
||||||
- update
|
|
||||||
- patch
|
|
||||||
- watch
|
|
||||||
- delete
|
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- pods
|
- pods
|
||||||
|
- nodes
|
||||||
|
- routes
|
||||||
|
- extensions
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- get
|
- get
|
||||||
|
|||||||
@ -28,10 +28,32 @@ spec:
|
|||||||
{{ toYaml .Values.caddyingresscontroller.deployment.labels | indent 8 }}
|
{{ toYaml .Values.caddyingresscontroller.deployment.labels | indent 8 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
|
serviceAccountName: {{ .Values.serviceAccountName }}
|
||||||
containers:
|
containers:
|
||||||
- image: "{{ .Values.caddyingresscontroller.image.name }}:{{ .Values.caddyingresscontroller.image.tag }}"
|
- name: {{ .Values.name }}
|
||||||
|
image: "{{ .Values.caddyingresscontroller.image.name }}:{{ .Values.caddyingresscontroller.image.tag }}"
|
||||||
imagePullPolicy: {{ .Values.caddyingresscontroller.image.pullPolicy }}
|
imagePullPolicy: {{ .Values.caddyingresscontroller.image.pullPolicy }}
|
||||||
name: {{ .Values.name }}
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: true
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
# www-data -> 33
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
{{- if .Values.minikube }}
|
||||||
|
hostPort: 80 # optional, required if running in minikube
|
||||||
|
{{- end }}
|
||||||
|
- name: https
|
||||||
|
containerPort: 443
|
||||||
|
{{- if .Values.minikube }}
|
||||||
|
hostPort: 443 # optional, required if running in minikube
|
||||||
|
{{- end }}
|
||||||
env:
|
env:
|
||||||
- name: POD_NAME
|
- name: POD_NAME
|
||||||
valueFrom:
|
valueFrom:
|
||||||
@ -44,5 +66,4 @@ spec:
|
|||||||
{{- if .Values.caddyingresscontroller.watchNamespace }}
|
{{- if .Values.caddyingresscontroller.watchNamespace }}
|
||||||
- name: KUBERNETES_NAMESPACE
|
- name: KUBERNETES_NAMESPACE
|
||||||
value: {{ .Values.caddyingresscontroller.watchNamespace | quote }}
|
value: {{ .Values.caddyingresscontroller.watchNamespace | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
serviceAccountName: {{ .Values.serviceAccountName }}
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
{{- if .Values.minikube }}
|
||||||
|
# we don't need a loadbalancer for local deployment purposes
|
||||||
|
{{ else }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.name }}
|
||||||
|
labels:
|
||||||
|
app: {{ .Values.name }}
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: http
|
||||||
|
- name: https
|
||||||
|
port: 443
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: https
|
||||||
|
selector:
|
||||||
|
app: {{ .Values.name }}
|
||||||
|
type: "LoadBalancer"
|
||||||
|
{{- end }}
|
||||||
@ -10,19 +10,17 @@ rules:
|
|||||||
- "extensions"
|
- "extensions"
|
||||||
resources:
|
resources:
|
||||||
- ingresses
|
- ingresses
|
||||||
- routes
|
- ingresses/status
|
||||||
verbs:
|
- secrets
|
||||||
- list
|
verbs: ["*"]
|
||||||
- get
|
|
||||||
- update
|
|
||||||
- patch
|
|
||||||
- watch
|
|
||||||
- delete
|
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
resources:
|
resources:
|
||||||
- services
|
- services
|
||||||
- pods
|
- pods
|
||||||
|
- nodes
|
||||||
|
- routes
|
||||||
|
- extensions
|
||||||
verbs:
|
verbs:
|
||||||
- list
|
- list
|
||||||
- get
|
- get
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
# Default values for caddyingresscontroller.
|
# Default values for the caddy ingress controller.
|
||||||
# This is a YAML-formatted file.
|
|
||||||
# Declare variables to be passed into your templates.
|
|
||||||
|
|
||||||
kubernetes:
|
kubernetes:
|
||||||
host: https://kubernetes.default
|
host: https://kubernetes.default
|
||||||
|
|
||||||
@ -24,11 +21,12 @@ caddyingresscontroller:
|
|||||||
version: "v0.1.0"
|
version: "v0.1.0"
|
||||||
# The name of the ServiceAccount to use.
|
# The name of the ServiceAccount to use.
|
||||||
# If not set and create is true, a name is generated using the fullname template
|
# If not set and create is true, a name is generated using the fullname template
|
||||||
name: caddyIngressController
|
name: caddy-ingress-controller
|
||||||
image:
|
image:
|
||||||
name: caddy/ingresscontroller
|
name: "gcr.io/danny-239313/ingresscontroller"
|
||||||
tag: "v0.1.0"
|
tag: "v0.1.0"
|
||||||
pullPolicy: IfNotPresent
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
name: "caddyingresscontroller"
|
name: "caddy-ingress-controller"
|
||||||
serviceAccountName: "caddyingresscontroller"
|
serviceAccountName: "caddy-ingress-controller"
|
||||||
|
minikube: false
|
||||||
147
pkg/storage/storage.go
Normal file
147
pkg/storage/storage.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/certmagic"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/klog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// matchLabels are attached to each resource so that they can be found in the future.
|
||||||
|
var matchLabels = map[string]string{
|
||||||
|
"manager": "caddy",
|
||||||
|
}
|
||||||
|
|
||||||
|
// labelSelector is the search string that will return all secrets managed by the caddy ingress controller.
|
||||||
|
var labelSelector = "manager=caddy"
|
||||||
|
|
||||||
|
// specialChars is a regex that matches all special characters except '.' and '-'.
|
||||||
|
var specialChars = regexp.MustCompile("[^0-9a-zA-Z.-]+")
|
||||||
|
|
||||||
|
// cleanKey strips all special characters that are not supported by kubernetes names and converts them to a '.'.
|
||||||
|
func cleanKey(key string) string {
|
||||||
|
return "caddy.ingress--" + specialChars.ReplaceAllString(key, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecretStorage facilitates storing certificates retrieved by certmagic in kubernetes secrets.
|
||||||
|
type SecretStorage struct {
|
||||||
|
Namespace string
|
||||||
|
KubeClient *kubernetes.Clientset
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertMagicStorage returns a certmagic storage type to be used by caddy.
|
||||||
|
func (s *SecretStorage) CertMagicStorage() (certmagic.Storage, error) {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists returns true if key exists in fs.
|
||||||
|
func (s *SecretStorage) Exists(key string) bool {
|
||||||
|
secrets, err := s.KubeClient.CoreV1().Secrets(s.Namespace).List(metav1.ListOptions{
|
||||||
|
FieldSelector: fmt.Sprintf("metadata.name=%v", cleanKey(key)),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
klog.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for _, i := range secrets.Items {
|
||||||
|
if i.ObjectMeta.Name == cleanKey(key) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves value at key. More than certs and keys are stored by certmagic in secrets.
|
||||||
|
func (s *SecretStorage) Store(key string, value []byte) error {
|
||||||
|
se := corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: cleanKey(key),
|
||||||
|
Labels: matchLabels,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"value": value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.KubeClient.CoreV1().Secrets(s.Namespace).Create(&se)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load retrieves the value at the given key.
|
||||||
|
func (s *SecretStorage) Load(key string) ([]byte, error) {
|
||||||
|
secret, err := s.KubeClient.CoreV1().Secrets(s.Namespace).Get(cleanKey(key), metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret.Data["value"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the value at the given key.
|
||||||
|
func (s *SecretStorage) Delete(key string) error {
|
||||||
|
err := s.KubeClient.CoreV1().Secrets(s.Namespace).Delete(cleanKey(key), &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns all keys that match prefix.
|
||||||
|
func (s *SecretStorage) List(prefix string, recursive bool) ([]string, error) {
|
||||||
|
var keys []string
|
||||||
|
|
||||||
|
secrets, err := s.KubeClient.CoreV1().Secrets(s.Namespace).List(metav1.ListOptions{LabelSelector: labelSelector})
|
||||||
|
if err != nil {
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO :- do we need to handle the recursive flag?
|
||||||
|
for _, secret := range secrets.Items {
|
||||||
|
key := secret.ObjectMeta.Name
|
||||||
|
if strings.HasPrefix(key, cleanKey(prefix)) {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns information about key.
|
||||||
|
func (s *SecretStorage) Stat(key string) (certmagic.KeyInfo, error) {
|
||||||
|
secret, err := s.KubeClient.CoreV1().Secrets(s.Namespace).Get(cleanKey(key), metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return certmagic.KeyInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certmagic.KeyInfo{
|
||||||
|
Key: key,
|
||||||
|
Modified: secret.GetCreationTimestamp().UTC(),
|
||||||
|
Size: int64(len(secret.Data["value"])),
|
||||||
|
IsTerminal: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock is a noop since the kubernetes client is thread safe.
|
||||||
|
func (s *SecretStorage) Lock(key string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock is a noop since the kubernetes client is thread safe.
|
||||||
|
func (s *SecretStorage) Unlock(key string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ apiVersion: skaffold/v1beta8
|
|||||||
kind: Config
|
kind: Config
|
||||||
build:
|
build:
|
||||||
artifacts:
|
artifacts:
|
||||||
- image: caddy/ingresscontroller
|
- image: gcr.io/danny-239313/ingresscontroller
|
||||||
deploy:
|
deploy:
|
||||||
kubectl:
|
kubectl:
|
||||||
manifests:
|
manifests:
|
||||||
@ -15,3 +15,4 @@ deploy:
|
|||||||
- kubernetes/generated/clusterrolebinding.yaml
|
- kubernetes/generated/clusterrolebinding.yaml
|
||||||
- kubernetes/generated/deployment.yaml
|
- kubernetes/generated/deployment.yaml
|
||||||
- kubernetes/generated/serviceaccount.yaml
|
- kubernetes/generated/serviceaccount.yaml
|
||||||
|
- kubernetes/generated/loadbalancer.yaml
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user