Skip to content
This repository has been archived by the owner on Aug 26, 2021. It is now read-only.

WIP: Add HAProxy Ingress support #228

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
## Requirements

- Kubernetes 1.2+
- Compatible ingress controller (nginx or GCE see [here](#ingress-controllers))
- Compatible ingress controller (nginx, HAProxy or GCE see [here](#ingress-controllers))
- Non-production use case :laughing:

## Usage
Expand All @@ -33,6 +33,7 @@

* [GCE](examples/gce/README.md)
* [nginx controller](examples/nginx/README.md)
* [HAProxy controller](/examples/haproxy)

The default value of `LEGO_URL` is the Let's Encrypt **staging environment**. If you want to get "real" certificates you have to configure their production env.

Expand Down Expand Up @@ -88,6 +89,11 @@ Please note:
- available through image `gcr.io/google_containers/nginx-ingress-controller`
- fully supports kube-lego from version 0.8 onwards

### [HAProxy Ingress controller](https://github.com/jcmoraisjr/haproxy-ingress)

- available through image `quay.io/jcmoraisjr/haproxy-ingress`
- fully supports kube-lego from version 0.3 onwards

### [GCE Loadbalancers](https://github.com/kubernetes/ingress/tree/master/controllers/gce)

- you don't have to maintain the ingress controller yourself, you pay GCE to do that for you
Expand All @@ -105,13 +111,16 @@ Please note:
| `LEGO_SECRET_NAME` | n | `kube-lego-account` | Name of the secret in the same namespace that contains ACME account secret |
| `LEGO_SERVICE_SELECTOR` | n | `kube-lego` | Set the service selector to the the kube-lego pod |
| `LEGO_SERVICE_NAME_NGINX` | n | `kube-lego-nginx` | Service name for NGINX ingress |
| `LEGO_SERVICE_NAME_HAPROXY` | n | `kube-lego-haproxy` | Service name for HAProxy ingress |
| `LEGO_SERVICE_NAME_GCE` | n | `kube-lego-gce` | Service name for GCE ingress |
| `LEGO_SUPPORTED_INGRESS_CLASS` | n | `nginx,gce` | Specify the supported ingress class |
| `LEGO_SUPPORTED_INGRESS_PROVIDER` | n | `nginx,gce` | Specify the supported ingress provider |
| `LEGO_SUPPORTED_INGRESS_CLASS` | n | `nginx,haproxy,gce` | Specify the supported ingress class |
| `LEGO_SUPPORTED_INGRESS_PROVIDER` | n | `nginx,haproxy,gce` | Specify the supported ingress provider |
| `LEGO_INGRESS_NAME_NGINX` | n | `kube-lego-nginx` | Ingress name which contains the routing for HTTP verification for nginx ingress |
| `LEGO_INGRESS_NAME_HAPROXY` | n | `kube-lego-haproxy` | Ingress name which contains the routing for HTTP verification for HAProxy ingress |
| `LEGO_PORT` | n | `8080` | Port where this daemon is listening for verifcation calls (HTTP method)|
| `LEGO_CHECK_INTERVAL` | n | `8h` | Interval for periodically certificate checks (to find expired certs)|
| `LEGO_MINIMUM_VALIDITY` | n | `720h` (30 days) | Request a renewal when the remaining certificate validity falls below that value|
| `LEGO_WAIT_CHALLENGE_URL` | n | Check instead wait | Time with suffix, eg `10s`, to wait for the updating of the challenge URL. This will skip the checking if declared. Useful if for some reason the container network cannot reach the public URL. |
| `LEGO_DEFAULT_INGRESS_CLASS` | n | `nginx` | Default ingress class for resources without specification|
| `LEGO_KUBE_API_URL` | n | `http://127.0.0.1:8080` | API server URL |
| `LEGO_LOG_LEVEL` | n | `info` | Set log level (`debug`, `info`, `warn` or `error`) |
Expand All @@ -122,6 +131,7 @@ Please note:
## Full deployment examples

- [Nginx Ingress Controller](examples/nginx/)
- [HAProxy Ingress controller](/examples/haproxy)
- [GCE Load Balancers](examples/gce/)

## Troubleshooting
Expand Down
36 changes: 36 additions & 0 deletions examples/haproxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# kube-lego example

This document demonstrates how to deploy kube-lego to the
[HAProxy Ingress](https://github.com/jcmoraisjr/haproxy-ingress) controller.

## Deploy the Ingress controller

Follow the [deployment instructions](https://github.com/kubernetes/ingress/tree/master/examples/deployment/haproxy)
including the deployment of the optional web app for testing.

## Deploy kube-lego

The following instruction will create the kube-lego deployment on it's own namespace.
Be aware that kube-lego creates it's related service on its own.

* Change `LEGO_EMAIL` to your email address
* Uncomment `LEGO_URL` to use the production API

```console
kubectl create ns kube-lego
kubectl create -f deployment.yaml
```

## Enable kube-lego in the testing application

This will add a TLS secret name and tls-acme annotation to the ingress resource created
in the deployment instruction.

* Change both `echo.example.com` to the public domain of your Ingress controller

```console
kubectl replace -f app-ingress.yaml
```

The `app-tls` secret and the https url should be updated. Check the log output of
HAProxy Ingress and kube-lego pods if this doesn't happen.
20 changes: 20 additions & 0 deletions examples/haproxy/app-ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: app
annotations:
kubernetes.io/tls-acme: "true"
kubernetes.io/ingress.class: "haproxy"
spec:
tls:
- hosts:
- echo.example.com
secretName: app-tls
rules:
- host: echo.example.com
http:
paths:
- path: /
backend:
serviceName: http-svc
servicePort: 8080
45 changes: 45 additions & 0 deletions examples/haproxy/lego-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kube-lego
namespace: kube-lego
spec:
selector:
matchLabels:
app: kube-lego
template:
metadata:
labels:
app: kube-lego
spec:
containers:
- name: kube-lego
## HAProxy support isn't on the stable release yet!
image: jetstack/kube-lego:canary
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
## Use HAProxy Ingress
- name: LEGO_DEFAULT_INGRESS_CLASS
value: haproxy
## Specify your email address
- name: LEGO_EMAIL
value: [email protected]
## Uncomment LEGO_URL to use the production API - default is to use staging
# - name: LEGO_URL
# value: https://acme-v01.api.letsencrypt.org/directory
- name: LEGO_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LEGO_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
8 changes: 7 additions & 1 deletion pkg/acme/cert_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ func (a *Acme) testReachablilty(domain string) error {
}

func (a *Acme) verifyDomain(domain string) (auth *acme.Authorization, err error) {
err = a.testReachablilty(domain)
sleep := a.kubelego.LegoWaitChallengeURL()
if sleep < 0 {
err = a.testReachablilty(domain)
} else {
time.Sleep(sleep)
err = nil
}
if err != nil {
return nil, fmt.Errorf("reachability test failed: %s", err)
}
Expand Down
37 changes: 29 additions & 8 deletions pkg/kubelego/kubelego.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ func (kl *KubeLego) Init() {
kl.legoIngressProvider["gce"] = gce.New(kl)
break
case "nginx":
kl.legoIngressProvider["nginx"] = nginx.New(kl)
kl.legoIngressProvider["nginx"] = nginx.New(kl, "nginx", kl.legoIngressNameNginx, kl.legoServiceNameNginx)
break
case "haproxy":
kl.legoIngressProvider["haproxy"] = nginx.New(kl, "haproxy", kl.legoIngressNameHAProxy, kl.legoServiceNameHAProxy)
break
default:
kl.Log().Warnf("Unsupported provider [%s], please add a handler in kubelego.go#Init()", provider)
Expand Down Expand Up @@ -196,9 +199,6 @@ func (kl *KubeLego) LegoDefaultIngressClass() string {
return kl.legoDefaultIngressClass
}

func (kl *KubeLego) LegoIngressNameNginx() string {
return kl.legoIngressNameNginx
}
func (kl *KubeLego) LegoSupportedIngressClass() []string {
return kl.legoSupportedIngressClass
}
Expand All @@ -207,10 +207,6 @@ func (kl *KubeLego) LegoSupportedIngressProvider() []string {
return kl.legoSupportedIngressProvider
}

func (kl *KubeLego) LegoServiceNameNginx() string {
return kl.legoServiceNameNginx
}

func (kl *KubeLego) LegoServiceNameGce() string {
return kl.legoServiceNameGce
}
Expand All @@ -219,6 +215,10 @@ func (kl *KubeLego) LegoMinimumValidity() time.Duration {
return kl.legoMinimumValidity
}

func (kl *KubeLego) LegoWaitChallengeURL() time.Duration {
return kl.legoWaitChallengeURL
}

func (kl *KubeLego) LegoCheckInterval() time.Duration {
return kl.legoCheckInterval
}
Expand Down Expand Up @@ -281,6 +281,11 @@ func (kl *KubeLego) paramsLego() error {
}
}

kl.legoServiceNameHAProxy = os.Getenv("LEGO_SERVICE_NAME_HAPROXY")
if len(kl.legoServiceNameHAProxy) == 0 {
kl.legoServiceNameHAProxy = "kube-lego-haproxy"
}

kl.legoServiceNameGce = os.Getenv("LEGO_SERVICE_NAME_GCE")
if len(kl.legoServiceNameGce) == 0 {
kl.legoServiceNameGce = "kube-lego-gce"
Expand Down Expand Up @@ -318,6 +323,11 @@ func (kl *KubeLego) paramsLego() error {
}
}

kl.legoIngressNameHAProxy = os.Getenv("LEGO_INGRESS_NAME_HAPROXY")
if len(kl.legoIngressNameHAProxy) == 0 {
kl.legoIngressNameHAProxy = "kube-lego-haproxy"
}

checkIntervalString := os.Getenv("LEGO_CHECK_INTERVAL")
if len(checkIntervalString) == 0 {
kl.legoCheckInterval = 8 * time.Hour
Expand Down Expand Up @@ -351,6 +361,17 @@ func (kl *KubeLego) paramsLego() error {
kl.legoMinimumValidity = d
}

waitChallengeURL := os.Getenv("LEGO_WAIT_CHALLENGE_URL")
if len(waitChallengeURL) == 0 {
kl.legoWaitChallengeURL = -1
} else {
w, err := time.ParseDuration(waitChallengeURL)
if err != nil {
return err
}
kl.legoWaitChallengeURL = w
}

httpPortStr := os.Getenv("LEGO_PORT")
if len(httpPortStr) == 0 {
kl.legoHTTPPort = intstr.FromInt(8080)
Expand Down
3 changes: 3 additions & 0 deletions pkg/kubelego/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ type KubeLego struct {
legoEmail string
legoSecretName string
legoIngressNameNginx string
legoIngressNameHAProxy string
legoNamespace string
legoPodIP net.IP
legoServiceNameNginx string
legoServiceNameHAProxy string
legoServiceNameGce string
legoSupportedIngressClass []string
legoSupportedIngressProvider []string
legoHTTPPort intstr.IntOrString
legoCheckInterval time.Duration
legoMinimumValidity time.Duration
legoWaitChallengeURL time.Duration
legoDefaultIngressClass string
legoDefaultIngressProvider string
legoKubeApiURL string
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubelego_const/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const AnnotationSslRedirect = "ingress.kubernetes.io/ssl-redirect"
const AnnotationKubeLegoManaged = "kubernetes.io/kube-lego-managed"
const AnnotationWhitelistSourceRange = "ingress.kubernetes.io/whitelist-source-range"

var SupportedIngressClasses = []string{"nginx", "gce"}
var SupportedIngressProviders = []string{"nginx", "gce"}
var SupportedIngressClasses = []string{"nginx", "haproxy", "gce"}
var SupportedIngressProviders = []string{"nginx", "haproxy", "gce"}
var AnnotationEnabled = "kubernetes.io/tls-acme"
var LegoServiceSelector = "kube-lego"
3 changes: 1 addition & 2 deletions pkg/kubelego_const/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ type KubeLego interface {
LegoURL() string
LegoNamespace() string
LegoWatchNamespace() string
LegoIngressNameNginx() string
LegoServiceNameNginx() string
LegoServiceNameGce() string
LegoDefaultIngressClass() string
LegoSupportedIngressClass() []string
LegoSupportedIngressProvider() []string
LegoCheckInterval() time.Duration
LegoMinimumValidity() time.Duration
LegoWaitChallengeURL() time.Duration
LegoPodIP() net.IP
IngressProvider(string) (IngressProvider, error)
Version() string
Expand Down
2 changes: 0 additions & 2 deletions pkg/mocks/kubelego.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ func DummyKubeLego(c *gomock.Controller) *MockKubeLego {
kl.EXPECT().LegoNamespace().AnyTimes().Return("kube-lego")
kl.EXPECT().LegoWatchNamespace().AnyTimes().Return(k8sApi.NamespaceAll)
kl.EXPECT().LegoPodIP().AnyTimes().Return(net.ParseIP("1.2.3.4"))
kl.EXPECT().LegoIngressNameNginx().AnyTimes().Return("kube-lego-nginx")
kl.EXPECT().LegoServiceNameNginx().AnyTimes().Return("kube-lego-nginx")
kl.EXPECT().LegoServiceNameGce().AnyTimes().Return("kube-lego-gce")
kl.EXPECT().LegoDefaultIngressClass().AnyTimes().Return("nginx")
kl.EXPECT().LegoDefaultIngressProvider().AnyTimes().Return("nginx")
Expand Down
30 changes: 10 additions & 20 deletions pkg/mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,6 @@ func (_mr *_MockKubeLegoRecorder) LegoWatchNamespace() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LegoWatchNamespace")
}

func (_m *MockKubeLego) LegoIngressNameNginx() string {
ret := _m.ctrl.Call(_m, "LegoIngressNameNginx")
ret0, _ := ret[0].(string)
return ret0
}

func (_mr *_MockKubeLegoRecorder) LegoIngressNameNginx() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LegoIngressNameNginx")
}

func (_m *MockKubeLego) LegoServiceNameNginx() string {
ret := _m.ctrl.Call(_m, "LegoServiceNameNginx")
ret0, _ := ret[0].(string)
return ret0
}

func (_mr *_MockKubeLegoRecorder) LegoServiceNameNginx() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LegoServiceNameNginx")
}

func (_m *MockKubeLego) LegoServiceNameGce() string {
ret := _m.ctrl.Call(_m, "LegoServiceNameGce")
ret0, _ := ret[0].(string)
Expand Down Expand Up @@ -207,6 +187,16 @@ func (_mr *_MockKubeLegoRecorder) LegoMinimumValidity() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LegoMinimumValidity")
}

func (_m *MockKubeLego) LegoWaitChallengeURL() time.Duration {
ret := _m.ctrl.Call(_m, "LegoWaitChallengeURL")
ret0, _ := ret[0].(time.Duration)
return ret0
}

func (_mr *_MockKubeLegoRecorder) LegoWaitChallengeURL() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "LegoWaitChallengeURL")
}

func (_m *MockKubeLego) LegoPodIP() net.IP {
ret := _m.ctrl.Call(_m, "LegoPodIP")
ret0, _ := ret[0].(net.IP)
Expand Down
Loading