From cf2faf263839b52c63d425d46796ef76f5b8f0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Thu, 7 Apr 2016 00:20:37 +0200 Subject: [PATCH 01/11] upstream merge --- Longhelp.md | 44 ++++++++++++++++++++++++++++++++++ README.md | 2 +- config.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ marathon_lb.py | 62 ++++++++++++++++++++++++++++++++++++------------ 4 files changed, 156 insertions(+), 16 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index d4cdc0f8..ffe7a0d5 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -329,6 +329,19 @@ frontend {backend} bind {bindAddr}:{servicePort}{sslCert}{bindOptions} mode {mode} ``` + +## `HAPROXY_USERLIST_HEAD` + *Overridable* + +Specified as `HAPROXY_USERLIST_HEAD` template or with label `HAPROXY_{n}_USERLIST_HEAD`. + +Defines the userlist for basic http auth to for this frontend. + + +**Default template for `HAPROXY_USERLIST_HEAD`:** +``` +userlist user_{backend}
 user {user} password {passwd} +``` ## `HAPROXY_HEAD` *Global* @@ -396,6 +409,21 @@ for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. ``` use_backend {backend} if {{ ssl_fc_sni {hostname} }} ``` +## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_WITH_AUTH`. + +The ACLs that performs the SNI based hostname matching and the HTTP basic auth for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. + + +**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH`:** +``` + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} + use_backend {backend} if {{ ssl_fc_sni {hostname} }} +``` + ## `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` *Overridable* @@ -496,6 +524,22 @@ of the `HAPROXY_HTTP_FRONTEND_HEAD` acl host_{cleanedUpHostname} hdr(host) -i {hostname} use_backend {backend} if host_{cleanedUpHostname} ``` +## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH`. + +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTP_FRONTEND_HEAD` and HTTP basic auth + + +**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH`:** +``` + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +``` ## `HAPROXY_HTTP_FRONTEND_ACL_ONLY` *Overridable* diff --git a/README.md b/README.md index 4af394aa..0ba8343e 100644 --- a/README.md +++ b/README.md @@ -245,4 +245,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` + ``` \ No newline at end of file diff --git a/config.py b/config.py index f6d55590..41a9af13 100644 --- a/config.py +++ b/config.py @@ -91,6 +91,17 @@ def load(self): description='''\ The head of the HAProxy config. This contains global settings and defaults. +''')) + + self.add_template( + ConfigTemplate(name='HAPROXY_USERLIST_HEAD', + value=''' +userlist user_{backend} + user {user} password {passwd} +''', + overridable=True, + description='''\ +The userlist for basic HTTP auth. ''')) self.add_template( @@ -209,6 +220,20 @@ def load(self): description='''\ The ACL that glues a backend to the corresponding virtual host of the `HAPROXY_HTTP_FRONTEND_HEAD` +''')) + + self.add_template( + ConfigTemplate(name='HTTP_FRONTEND_ACL_WITH_AUTH', + value='''\ + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +''', + overridable=True, + description='''\ +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTP_FRONTEND_HEAD` thru HTTP basic auth. ''')) self.add_template( @@ -303,6 +328,19 @@ def load(self): description='''\ The ACL that performs the SNI based hostname matching for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. +''')) + + self.add_template( + ConfigTemplate(name='HTTPS_FRONTEND_ACL_WITH_AUTH', + value='''\ + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} + use_backend {backend} if {{ ssl_fc_sni {hostname} }} +''', + overridable=True, + description='''\ +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTPS_FRONTEND_HEAD` thru HTTP basic auth. ''')) self.add_template( @@ -615,6 +653,21 @@ def haproxy_http_frontend_appid_head(self): def haproxy_https_frontend_head(self): return self.t['HTTPS_FRONTEND_HEAD'].value + def haproxy_userlist_head(self, app): + if 'HAPROXY_{0}_USERLIST_HEAD' in app.labels: + return app.labels['HAPROXY_{0}_USERLIST_HEAD'] + return self.t['HAPROXY_USERLIST_HEAD'].value + + def haproxy_http_frontend_acl_with_auth(self, app): + if 'HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH' in app.labels: + return app.labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH'] + return self.t['HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH'].value + + def haproxy_https_frontend_acl_with_auth(self, app): + if 'HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH' in app.labels: + return app.labels['HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH'] + return self.t['HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH'].value + def haproxy_frontend_head(self, app): if 'FRONTEND_HEAD' in app.labels: return app.labels['HAPROXY_{0}_FRONTEND_HEAD'] @@ -783,6 +836,8 @@ def set_sticky(x, k, v): def set_redirect_http_to_https(x, k, v): x.redirectHttpToHttps = string_to_bool(v) +def set_auth(x, k, v): + x.authRealm, x.authUser, x.authPasswd = v.split(':') def set_use_hsts(x, k, v): x.useHsts = string_to_bool(v) @@ -925,6 +980,12 @@ def __init__(self, name, func, description, perServicePort=True): func=set_path, description='''\ ''')) +labels.append(Label(name='AUTH', + func=set_auth, + description='''\ +The http basic auth definition. +Format : "realm:username:encryptedpassword". + ''')) labels.append(Label(name='STICKY', func=set_sticky, description='''\ @@ -1017,6 +1078,9 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='FRONTEND_HEAD', func=set_label, description='')) +labels.append(Label(name='USERLIST_HEAD', + func=set_label, + description='')) labels.append(Label(name='BACKEND_REDIRECT_HTTP_TO_HTTPS', func=set_label, description='')) diff --git a/marathon_lb.py b/marathon_lb.py index 32a3a7a3..0409f600 100755 --- a/marathon_lb.py +++ b/marathon_lb.py @@ -77,6 +77,9 @@ def __init__(self, appId, servicePort, healthCheck): self.redirpath = None self.haproxy_groups = frozenset() self.path = None + self.authRealm = None + self.authUser = None + self.authPasswd = None self.sticky = False self.redirectHttpToHttps = False self.useHsts = False @@ -268,6 +271,7 @@ def config(apps, groups, bind_http_https, ssl_certs, templater): sslCerts=" ".join(map(lambda cert: "crt " + cert, _ssl_certs)) ) + userlists = str() frontends = str() backends = str() http_appid_frontends = templater.haproxy_http_frontend_appid_head @@ -296,6 +300,14 @@ def config(apps, groups, bind_http_https, ssl_certs, templater): if app.hostname: app.mode = 'http' + if app.authUser: + userlist_head = templater.haproxy_userlist_head(app) + userlists += userlist_head.format( + backend=backend, + user=app.authUser, + passwd=app.authPasswd + ) + frontend_head = templater.haproxy_frontend_head(app) frontends += frontend_head.format( bindAddr=app.bindAddr, @@ -467,6 +479,7 @@ def config(apps, groups, bind_http_https, ssl_certs, templater): "ignoring this backend", backendServer.host) + config += userlists if bind_http_https: config += http_frontends config += http_appid_frontends @@ -697,21 +710,40 @@ def generateHttpVhostAcl(templater, app, backend): ) staging_http_frontends += frontend else: - http_frontend_acl = templater.haproxy_http_frontend_acl(app) - staging_http_frontends += http_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - backend=backend - ) - https_frontend_acl = templater.haproxy_https_frontend_acl(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - backend=backend - ) - + if app.authRealm: + http_frontend_acl = templater.haproxy_http_frontend_acl_with_auth(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + http_frontend_acl = templater.haproxy_http_frontend_acl(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + backend=backend + ) + if app.authRealm: + https_frontend_acl = templater.haproxy_https_frontend_acl_with_auth(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + https_frontend_acl = templater.haproxy_https_frontend_acl(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + backend=backend + ) return (staging_http_frontends, staging_https_frontends) From cb6802bab51d3006b7165edc6363e5e38a66f921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Thu, 7 Apr 2016 10:05:12 +0200 Subject: [PATCH 02/11] add http basic auth support --- Longhelp.md | 204 +++++++++++++++++++++----- config.py | 183 +++++++++++++++++++++-- marathon_lb.py | 272 +++++++++++++++++++++++----------- tests/test_marathon_lb.py | 301 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 823 insertions(+), 137 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index ffe7a0d5..4237bd36 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -329,19 +329,6 @@ frontend {backend} bind {bindAddr}:{servicePort}{sslCert}{bindOptions} mode {mode} ``` - -## `HAPROXY_USERLIST_HEAD` - *Overridable* - -Specified as `HAPROXY_USERLIST_HEAD` template or with label `HAPROXY_{n}_USERLIST_HEAD`. - -Defines the userlist for basic http auth to for this frontend. - - -**Default template for `HAPROXY_USERLIST_HEAD`:** -``` -userlist user_{backend}
 user {user} password {passwd} -``` ## `HAPROXY_HEAD` *Global* @@ -409,12 +396,25 @@ for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. ``` use_backend {backend} if {{ ssl_fc_sni {hostname} }} ``` +## `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH`. + +Same as HTTP_FRONTEND_ACL_ONLY_WITH_PATH, but for HTTPS. + + +**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH`:** +``` + acl path_{backend} path_beg {path} +``` ## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` *Overridable* Specified as `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_WITH_AUTH`. -The ACLs that performs the SNI based hostname matching and the HTTP basic auth for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTPS_FRONTEND_HEAD` thru HTTP basic auth. **Default template for `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH`:** @@ -423,18 +423,20 @@ The ACLs that performs the SNI based hostname matching and the HTTP basic auth f http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} use_backend {backend} if {{ ssl_fc_sni {hostname} }} ``` - -## `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` +## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH` *Overridable* -Specified as `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH`. +Specified as `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH`. -Same as HTTP_FRONTEND_ACL_ONLY_WITH_PATH, but for HTTPS. +The ACL that glues a backend to the corresponding virtual host with path +of the `HAPROXY_HTTPS_FRONTEND_HEAD` thru HTTP basic auth. -**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH`:** +**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH`:** ``` - acl path_{backend} path_beg {path} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if {{ ssl_fc_sni {hostname} }} path_{backend} ``` ## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_PATH` *Overridable* @@ -449,6 +451,30 @@ for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. ``` use_backend {backend} if {{ ssl_fc_sni {hostname} }} path_{backend} ``` +## `HAPROXY_HTTPS_FRONTEND_AUTH_ACL_ONLY` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_AUTH_ACL_ONLY` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_AUTH_ACL_ONLY`. + +The http auth ACL to the corresponding virtual host. + + +**Default template for `HAPROXY_HTTPS_FRONTEND_AUTH_ACL_ONLY`:** +``` + acl auth_{cleanedUpHostname} http_auth(user_{backend}) +``` +## `HAPROXY_HTTPS_FRONTEND_AUTH_REQUEST_ONLY` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_AUTH_REQUEST_ONLY` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_AUTH_REQUEST_ONLY`. + +The http auth request to the corresponding virtual host. + + +**Default template for `HAPROXY_HTTPS_FRONTEND_AUTH_REQUEST_ONLY`:** +``` + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} +``` ## `HAPROXY_HTTPS_FRONTEND_HEAD` *Global* @@ -467,6 +493,20 @@ frontend marathon_https_in bind *:443 ssl {sslCerts} mode http ``` +## `HAPROXY_HTTPS_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH`. + +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH` which +glues the acl names to the appropriate backend + + +**Default template for `HAPROXY_HTTPS_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH`:** +``` + http-request auth realm "{realm}" if host_{cleanedUpHostname} path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +``` ## `HAPROXY_HTTP_BACKEND_PROXYPASS` *Overridable* @@ -524,22 +564,6 @@ of the `HAPROXY_HTTP_FRONTEND_HEAD` acl host_{cleanedUpHostname} hdr(host) -i {hostname} use_backend {backend} if host_{cleanedUpHostname} ``` -## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` - *Overridable* - -Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH`. - -The ACL that glues a backend to the corresponding virtual host -of the `HAPROXY_HTTP_FRONTEND_HEAD` and HTTP basic auth - - -**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH`:** -``` - acl host_{cleanedUpHostname} hdr(host) -i {hostname} - acl auth_{cleanedUpHostname} http_auth(user_{backend}) - http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} - use_backend {backend} if host_{cleanedUpHostname} -``` ## `HAPROXY_HTTP_FRONTEND_ACL_ONLY` *Overridable* @@ -570,6 +594,55 @@ vhosts routing to the same backend ``` acl path_{backend} path_beg {path} ``` +## `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH`. + +Define the ACL matching a particular hostname with path and auth, but unlike +`HAPROXY_HTTP_FRONTEND_ACL_WITH_PATH`, only do the ACL portion. Does not glue +the ACL to the backend. This is useful only in the case of multiple +vhosts routing to the same backend + + +**Default template for `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH`:** +``` + acl path_{backend} path_beg {path} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) +``` +## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH`. + +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTP_FRONTEND_HEAD` thru HTTP basic auth. + + +**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH`:** +``` + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +``` +## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH`. + +The ACL that glues a backend to the corresponding virtual host with path +of the `HAPROXY_HTTP_FRONTEND_HEAD` thru HTTP basic auth. + + +**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH`:** +``` + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + acl path_{backend} path_beg {path} + http-request auth realm "{realm}" if host_{cleanedUpHostname} path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +``` ## `HAPROXY_HTTP_FRONTEND_ACL_WITH_PATH` *Overridable* @@ -649,6 +722,21 @@ glues the acl name to the appropriate backend. ``` use_backend {backend} if host_{cleanedUpHostname} ``` +## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH`. + +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY` which +glues the acl name to the appropriate backend, and add http basic auth. + + +**Default template for `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH`:** +``` + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +``` ## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH` *Overridable* @@ -662,9 +750,46 @@ glues the acl names to the appropriate backend ``` use_backend {backend} if host_{cleanedUpHostname} path_{backend} ``` +## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH`. + +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH` which +glues the acl names to the appropriate backend + + +**Default template for `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH`:** +``` + http-request auth realm "{realm}" if host_{cleanedUpHostname} path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +``` +## `HAPROXY_USERLIST_HEAD` + *Overridable* + +Specified as `HAPROXY_USERLIST_HEAD` template or with label `HAPROXY_{n}_USERLIST_HEAD`. + +The userlist for basic HTTP auth. + + +**Default template for `HAPROXY_USERLIST_HEAD`:** +``` + +userlist user_{backend} + user {user} password {passwd} +``` ## Other Labels These labels may be used to configure other app settings. +## `HAPROXY_{n}_AUTH` + *per service port* + +Specified as `HAPROXY_{n}_AUTH`. + +The http basic auth definition. + +Ex: `HAPROXY_0_AUTH = realm:username:encryptedpassword` + ## `HAPROXY_{n}_BALANCE` *per service port* @@ -777,6 +902,13 @@ it falls back to default `HAPROXY_GROUP` and gets associated with Load balancers with the group '*' will collect all groups. +## `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH` + *per service port* + +Specified as `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH`. + + + ## `HAPROXY_{n}_MODE` *per service port* diff --git a/config.py b/config.py index 41a9af13..d6f35a1e 100644 --- a/config.py +++ b/config.py @@ -94,10 +94,10 @@ def load(self): ''')) self.add_template( - ConfigTemplate(name='HAPROXY_USERLIST_HEAD', + ConfigTemplate(name='USERLIST_HEAD', value=''' userlist user_{backend} - user {user} password {passwd} + user {user} password {passwd} ''', overridable=True, description='''\ @@ -227,7 +227,8 @@ def load(self): value='''\ acl host_{cleanedUpHostname} hdr(host) -i {hostname} acl auth_{cleanedUpHostname} http_auth(user_{backend}) - http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + http-request auth realm "{realm}" if host_{cleanedUpHostname}\ + !auth_{cleanedUpHostname} use_backend {backend} if host_{cleanedUpHostname} ''', overridable=True, @@ -258,6 +259,20 @@ def load(self): description='''\ This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY` which glues the acl name to the appropriate backend. +''')) + + self.add_template( + ConfigTemplate(name='HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH', + value='''\ + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} \ +!auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +''', + overridable=True, + description='''\ +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY` which +glues the acl name to the appropriate backend, and add http basic auth. ''')) self.add_template( @@ -271,6 +286,22 @@ def load(self): description='''\ The ACL that glues a backend to the corresponding virtual host with path of the `HAPROXY_HTTP_FRONTEND_HEAD`. +''')) + + self.add_template( + ConfigTemplate(name='HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH', + value='''\ + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + acl path_{backend} path_beg {path} + http-request auth realm "{realm}" if host_{cleanedUpHostname} \ +path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +''', + overridable=True, + description='''\ +The ACL that glues a backend to the corresponding virtual host with path +of the `HAPROXY_HTTP_FRONTEND_HEAD` thru HTTP basic auth. ''')) self.add_template( @@ -284,6 +315,20 @@ def load(self): `HAPROXY_HTTP_FRONTEND_ACL_WITH_PATH`, only do the ACL portion. Does not glue the ACL to the backend. This is useful only in the case of multiple vhosts routing to the same backend +''')) + + self.add_template( + ConfigTemplate(name='HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH', + value='''\ + acl path_{backend} path_beg {path} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) +''', + overridable=True, + description='''\ +Define the ACL matching a particular hostname with path and auth, but unlike +`HAPROXY_HTTP_FRONTEND_ACL_WITH_PATH`, only do the ACL portion. Does not glue +the ACL to the backend. This is useful only in the case of multiple +vhosts routing to the same backend ''')) self.add_template( @@ -307,6 +352,33 @@ def load(self): glues the acl names to the appropriate backend ''')) + self.add_template( + ConfigTemplate(name='\ +HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH', + value='''\ + http-request auth realm "{realm}" if host_{cleanedUpHostname} \ +path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +''', + overridable=True, + description='''\ +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH` which +glues the acl names to the appropriate backend +''')) + + self.add_template( + ConfigTemplate(name='\ +HTTPS_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH', + value='''\ + http-request auth realm "{realm}" if host_{cleanedUpHostname} \ +path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} path_{backend} +''', + overridable=True, + description='''\ +This is the counterpart to `HAPROXY_HTTP_FRONTEND_ACL_ONLY_WITH_PATH` which +glues the acl names to the appropriate backend +''')) self.add_template( ConfigTemplate(name='HTTP_FRONTEND_APPID_ACL', value='''\ @@ -334,13 +406,35 @@ def load(self): ConfigTemplate(name='HTTPS_FRONTEND_ACL_WITH_AUTH', value='''\ acl auth_{cleanedUpHostname} http_auth(user_{backend}) - http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} \ +!auth_{cleanedUpHostname} use_backend {backend} if {{ ssl_fc_sni {hostname} }} ''', overridable=True, description='''\ The ACL that glues a backend to the corresponding virtual host of the `HAPROXY_HTTPS_FRONTEND_HEAD` thru HTTP basic auth. +''')) + + self.add_template( + ConfigTemplate(name='HTTPS_FRONTEND_AUTH_ACL_ONLY', + value='''\ + acl auth_{cleanedUpHostname} http_auth(user_{backend}) +''', + overridable=True, + description='''\ +The http auth ACL to the corresponding virtual host. +''')) + + self.add_template( + ConfigTemplate(name='HTTPS_FRONTEND_AUTH_REQUEST_ONLY', + value='''\ + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} \ +!auth_{cleanedUpHostname} +''', + overridable=True, + description='''\ +The http auth request to the corresponding virtual host. ''')) self.add_template( @@ -352,6 +446,20 @@ def load(self): description='''\ The ACL that performs the SNI based hostname matching with path for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. +''')) + + self.add_template( + ConfigTemplate(name='HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH', + value='''\ + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} \ +path_{backend} !auth_{cleanedUpHostname} + use_backend {backend} if {{ ssl_fc_sni {hostname} }} path_{backend} +''', + overridable=True, + description='''\ +The ACL that glues a backend to the corresponding virtual host with path +of the `HAPROXY_HTTPS_FRONTEND_HEAD` thru HTTP basic auth. ''')) self.add_template( @@ -656,17 +764,29 @@ def haproxy_https_frontend_head(self): def haproxy_userlist_head(self, app): if 'HAPROXY_{0}_USERLIST_HEAD' in app.labels: return app.labels['HAPROXY_{0}_USERLIST_HEAD'] - return self.t['HAPROXY_USERLIST_HEAD'].value + return self.t['USERLIST_HEAD'].value def haproxy_http_frontend_acl_with_auth(self, app): if 'HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH' in app.labels: return app.labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH'] - return self.t['HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH'].value + return self.t['HTTP_FRONTEND_ACL_WITH_AUTH'].value def haproxy_https_frontend_acl_with_auth(self, app): if 'HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH' in app.labels: return app.labels['HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH'] - return self.t['HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH'].value + return self.t['HTTPS_FRONTEND_ACL_WITH_AUTH'].value + + def haproxy_http_frontend_acl_with_auth_and_path(self, app): + if 'HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH' in app.labels: + return \ + app.labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH'] + return self.t['HTTP_FRONTEND_ACL_WITH_AUTH_AND_PATH'].value + + def haproxy_https_frontend_acl_with_auth_and_path(self, app): + if 'HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH' in app.labels: + return \ + app.labels['HAPROXY_{0}_HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH'] + return self.t['HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH'].value def haproxy_frontend_head(self, app): if 'FRONTEND_HEAD' in app.labels: @@ -710,6 +830,12 @@ def haproxy_http_frontend_routing_only(self, app): return app.labels['HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY'] return self.t['HTTP_FRONTEND_ROUTING_ONLY'].value + def haproxy_http_frontend_routing_only_with_auth(self, app): + if 'HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH' in app.labels: + return app.\ + labels['HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH'] + return self.t['HTTP_FRONTEND_ROUTING_ONLY_WITH_AUTH'].value + def haproxy_http_frontend_acl_with_path(self, app): if 'HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_PATH' in app.labels: return app.labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_WITH_PATH'] @@ -720,6 +846,14 @@ def haproxy_http_frontend_acl_only_with_path(self, app): return app.labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_ONLY_WITH_PATH'] return self.t['HTTP_FRONTEND_ACL_ONLY_WITH_PATH'].value + def haproxy_http_frontend_acl_only_with_path_and_auth(self, app): + if 'HAPROXY_{0}_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH'\ + in app.labels: + return\ + app.\ + labels['HAPROXY_{0}_HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH'] + return self.t['HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH'].value + def haproxy_https_frontend_acl_only_with_path(self, app): if 'HAPROXY_{0}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH' in app.labels: return app.labels['HAPROXY_{0}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH'] @@ -731,6 +865,15 @@ def haproxy_http_frontend_routing_only_with_path(self, app): app.labels['HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH'] return self.t['HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH'].value + def haproxy_http_frontend_routing_only_with_path_and_auth(self, app): + if 'HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH'\ + in app.labels: + return\ + app.\ + labels[ + 'HAPROXY_{0}_HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH'] + return self.t['HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH'].value + def haproxy_http_frontend_appid_acl(self, app): if 'HAPROXY_{0}_HTTP_FRONTEND_APPID_ACL' in app.labels: return app.labels['HAPROXY_{0}_HTTP_FRONTEND_APPID_ACL'] @@ -836,9 +979,11 @@ def set_sticky(x, k, v): def set_redirect_http_to_https(x, k, v): x.redirectHttpToHttps = string_to_bool(v) + def set_auth(x, k, v): x.authRealm, x.authUser, x.authPasswd = v.split(':') + def set_use_hsts(x, k, v): x.useHsts = string_to_bool(v) @@ -899,6 +1044,12 @@ def __init__(self, name, func, description, perServicePort=True): self.description = description labels = [] +labels.append(Label(name='AUTH', + func=set_auth, + description='''\ +The http basic auth definition. + +Ex: `HAPROXY_0_AUTH = realm:username:encryptedpassword`''')) labels.append(Label(name='VHOST', func=set_hostname, description='''\ @@ -980,12 +1131,6 @@ def __init__(self, name, func, description, perServicePort=True): func=set_path, description='''\ ''')) -labels.append(Label(name='AUTH', - func=set_auth, - description='''\ -The http basic auth definition. -Format : "realm:username:encryptedpassword". - ''')) labels.append(Label(name='STICKY', func=set_sticky, description='''\ @@ -1096,18 +1241,30 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='HTTP_FRONTEND_ROUTING_ONLY', func=set_label, description='')) +labels.append(Label(name='HTTP_FRONTEND_ACL_WITH_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTP_FRONTEND_ACL_WITH_PATH', func=set_label, description='')) labels.append(Label(name='HTTP_FRONTEND_ACL_ONLY_WITH_PATH', func=set_label, description='')) +labels.append(Label(name='HTTP_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH', func=set_label, description='')) +labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH', func=set_label, description='')) +labels.append(Label(name='HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH_AND_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTP_FRONTEND_APPID_ACL', func=set_label, description='')) diff --git a/marathon_lb.py b/marathon_lb.py index 0409f600..4ba0ba6b 100755 --- a/marathon_lb.py +++ b/marathon_lb.py @@ -559,20 +559,44 @@ def generateHttpVhostAcl(templater, app, backend): '_' + app.appId[1:].replace('/', '_') if app.path: - # Set the path ACL if it exists - logger.debug("adding path acl, path=%s", app.path) - http_frontend_acl = \ - templater.haproxy_http_frontend_acl_only_with_path(app) - staging_http_frontends += http_frontend_acl.format( - path=app.path, - backend=backend - ) - https_frontend_acl = \ - templater.haproxy_https_frontend_acl_only_with_path(app) - staging_https_frontends += https_frontend_acl.format( - path=app.path, - backend=backend - ) + if app.authRealm: + # Set the path ACL if it exists + logger.debug("adding path acl, path=%s", app.path) + http_frontend_acl = \ + templater.\ + haproxy_http_frontend_acl_only_with_path_and_auth(app) + staging_http_frontends += http_frontend_acl.format( + path=app.path, + cleanedUpHostname=acl_name, + hostname=vhosts[0], + realm=app.authRealm, + backend=backend + ) + https_frontend_acl = \ + templater.\ + haproxy_https_frontend_acl_only_with_path(app) + staging_https_frontends += https_frontend_acl.format( + path=app.path, + cleanedUpHostname=acl_name, + hostname=vhosts[0], + realm=app.authRealm, + backend=backend + ) + else: + # Set the path ACL if it exists + logger.debug("adding path acl, path=%s", app.path) + http_frontend_acl = \ + templater.haproxy_http_frontend_acl_only_with_path(app) + staging_http_frontends += http_frontend_acl.format( + path=app.path, + backend=backend + ) + https_frontend_acl = \ + templater.haproxy_https_frontend_acl_only_with_path(app) + staging_https_frontends += https_frontend_acl.format( + path=app.path, + backend=backend + ) for vhost_hostname in vhosts: logger.debug("processing vhost %s", vhost_hostname) @@ -584,22 +608,45 @@ def generateHttpVhostAcl(templater, app, backend): # Tack on the SSL ACL as well if app.path: - https_frontend_acl = \ - templater.haproxy_https_frontend_acl_with_path(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=vhost_hostname, - appId=app.appId, - backend=backend - ) + if app.authRealm: + https_frontend_acl = templater.\ + haproxy_https_frontend_acl_with_auth_and_path(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=vhost_hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + https_frontend_acl = \ + templater.haproxy_https_frontend_acl_with_path(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=vhost_hostname, + appId=app.appId, + backend=backend + ) else: - https_frontend_acl = templater.haproxy_https_frontend_acl(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=vhost_hostname, - appId=app.appId, - backend=backend - ) + if app.authRealm: + https_frontend_acl = \ + templater.haproxy_https_frontend_acl_with_auth(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=vhost_hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + https_frontend_acl = templater.\ + haproxy_https_frontend_acl(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=vhost_hostname, + appId=app.appId, + backend=backend + ) # We've added the http acl lines, now route them to the same backend if app.redirectHttpToHttps: @@ -623,19 +670,39 @@ def generateHttpVhostAcl(templater, app, backend): ) staging_http_frontends += frontend elif app.path: - http_frontend_route = \ - templater.haproxy_http_frontend_routing_only_with_path(app) - staging_http_frontends += http_frontend_route.format( - cleanedUpHostname=acl_name, - backend=backend - ) + if app.authRealm: + http_frontend_route = \ + templater.\ + haproxy_http_frontend_routing_only_with_path_and_auth(app) + staging_http_frontends += http_frontend_route.format( + cleanedUpHostname=acl_name, + realm=app.authRealm, + backend=backend + ) + else: + http_frontend_route = \ + templater.haproxy_http_frontend_routing_only_with_path(app) + staging_http_frontends += http_frontend_route.format( + cleanedUpHostname=acl_name, + backend=backend + ) else: - http_frontend_route = \ - templater.haproxy_http_frontend_routing_only(app) - staging_http_frontends += http_frontend_route.format( - cleanedUpHostname=acl_name, - backend=backend - ) + if app.authRealm: + http_frontend_route = \ + templater.\ + haproxy_http_frontend_routing_only_with_auth(app) + staging_http_frontends += http_frontend_route.format( + cleanedUpHostname=acl_name, + realm=app.authRealm, + backend=backend + ) + else: + http_frontend_route = \ + templater.haproxy_http_frontend_routing_only(app) + staging_http_frontends += http_frontend_route.format( + cleanedUpHostname=acl_name, + backend=backend + ) else: # A single hostname in the VHOST label @@ -670,29 +737,55 @@ def generateHttpVhostAcl(templater, app, backend): ) staging_http_frontends += frontend else: - http_frontend_acl = \ - templater.haproxy_http_frontend_acl_with_path(app) - staging_http_frontends += http_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - path=app.path, - appId=app.appId, - backend=backend - ) + if app.authRealm: + http_frontend_acl = \ + templater.\ + haproxy_http_frontend_acl_with_auth_and_path(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + path=app.path, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + http_frontend_acl = \ + templater.haproxy_http_frontend_acl_with_path(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + path=app.path, + appId=app.appId, + backend=backend + ) https_frontend_acl = \ templater.haproxy_https_frontend_acl_only_with_path(app) staging_https_frontends += https_frontend_acl.format( path=app.path, backend=backend ) - https_frontend_acl = \ - templater.haproxy_https_frontend_acl_with_path(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - backend=backend - ) + if app.authRealm: + https_frontend_acl = \ + templater.\ + haproxy_https_frontend_acl_with_auth_and_path(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + path=app.path, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) + else: + https_frontend_acl = \ + templater.haproxy_https_frontend_acl_with_path(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + backend=backend + ) else: if app.redirectHttpToHttps: http_frontend_acl = \ @@ -711,39 +804,42 @@ def generateHttpVhostAcl(templater, app, backend): staging_http_frontends += frontend else: if app.authRealm: - http_frontend_acl = templater.haproxy_http_frontend_acl_with_auth(app) - staging_http_frontends += http_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - realm=app.authRealm, - backend=backend - ) + http_frontend_acl = \ + templater.haproxy_http_frontend_acl_with_auth(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) else: - http_frontend_acl = templater.haproxy_http_frontend_acl(app) - staging_http_frontends += http_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - backend=backend - ) + http_frontend_acl = \ + templater.haproxy_http_frontend_acl(app) + staging_http_frontends += http_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + backend=backend + ) if app.authRealm: - https_frontend_acl = templater.haproxy_https_frontend_acl_with_auth(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - realm=app.authRealm, - backend=backend - ) + https_frontend_acl = \ + templater.haproxy_https_frontend_acl_with_auth(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + realm=app.authRealm, + backend=backend + ) else: - https_frontend_acl = templater.haproxy_https_frontend_acl(app) - staging_https_frontends += https_frontend_acl.format( - cleanedUpHostname=acl_name, - hostname=app.hostname, - appId=app.appId, - backend=backend - ) + https_frontend_acl = templater.haproxy_https_frontend_acl(app) + staging_https_frontends += https_frontend_acl.format( + cleanedUpHostname=acl_name, + hostname=app.hostname, + appId=app.appId, + backend=backend + ) return (staging_http_frontends, staging_https_frontends) diff --git a/tests/test_marathon_lb.py b/tests/test_marathon_lb.py index 6f54b1cb..f03b9416 100644 --- a/tests/test_marathon_lb.py +++ b/tests/test_marathon_lb.py @@ -482,6 +482,307 @@ def test_config_simple_app_multiple_vhost_and_redirect(self): mode http use_backend nginx_10000 +backend nginx_10000 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + option httpchk GET / + timeout check 10s +''' + self.assertMultiLineEqual(config, expected) + + def test_config_simple_app_vhost_with_auth(self): + apps = dict() + groups = ['external'] + bind_http_https = True + ssl_certs = "" + templater = marathon_lb.ConfigTemplater() + + healthCheck = { + "path": "/", + "protocol": "HTTP", + "portIndex": 0, + "gracePeriodSeconds": 10, + "intervalSeconds": 2, + "timeoutSeconds": 10, + "maxConsecutiveFailures": 10, + "ignoreHttp1xx": False + } + app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) + app.hostname = "test.example.com" + app.groups = ['external'] + app.authRealm = "realm" + app.authUser = "testuser" + app.authPasswd = "testpasswd" + app.add_backend("1.1.1.1", 1024, False) + apps = [app] + + config = marathon_lb.config(apps, groups, bind_http_https, + ssl_certs, templater) + expected = self.base_config + ''' +userlist user_nginx_10000 + user testuser password testpasswd + +frontend marathon_http_in + bind *:80 + mode http + acl host_test_example_com_nginx hdr(host) -i test.example.com + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if host_test_example_com_nginx \ +!auth_test_example_com_nginx + use_backend nginx_10000 if host_test_example_com_nginx + +frontend marathon_http_appid_in + bind *:9091 + mode http + acl app__nginx hdr(x-marathon-app-id) -i /nginx + use_backend nginx_10000 if app__nginx + +frontend marathon_https_in + bind *:443 ssl crt /etc/ssl/mesosphere.com.pem + mode http + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test.example.com } \ +!auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test.example.com } + +frontend nginx_10000 + bind *:10000 + mode http + use_backend nginx_10000 + +backend nginx_10000 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + option httpchk GET / + timeout check 10s + server 1_1_1_1_1024 1.1.1.1:1024 check inter 2s fall 11 +''' + self.assertMultiLineEqual(config, expected) + + def test_config_simple_app_multiple_vhost_and_auth(self): + apps = dict() + groups = ['external'] + bind_http_https = True + ssl_certs = "" + templater = marathon_lb.ConfigTemplater() + + healthCheck = { + "path": "/", + "protocol": "HTTP", + "portIndex": 0, + "gracePeriodSeconds": 10, + "intervalSeconds": 2, + "timeoutSeconds": 10, + "maxConsecutiveFailures": 10, + "ignoreHttp1xx": False + } + app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) + app.hostname = "test.example.com,test" + app.authRealm = "realm" + app.authUser = "testuser" + app.authPasswd = "testpasswd" + app.groups = ['external'] + apps = [app] + + config = marathon_lb.config(apps, groups, bind_http_https, + ssl_certs, templater) + expected = self.base_config + ''' +userlist user_nginx_10000 + user testuser password testpasswd + +frontend marathon_http_in + bind *:80 + mode http + acl host_test_example_com_nginx hdr(host) -i test.example.com + acl host_test_example_com_nginx hdr(host) -i test + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if host_test_example_com_nginx \ +!auth_test_example_com_nginx + use_backend nginx_10000 if host_test_example_com_nginx + +frontend marathon_http_appid_in + bind *:9091 + mode http + acl app__nginx hdr(x-marathon-app-id) -i /nginx + use_backend nginx_10000 if app__nginx + +frontend marathon_https_in + bind *:443 ssl crt /etc/ssl/mesosphere.com.pem + mode http + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test.example.com } \ +!auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test.example.com } + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test } \ +!auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test } + +frontend nginx_10000 + bind *:10000 + mode http + use_backend nginx_10000 + +backend nginx_10000 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + option httpchk GET / + timeout check 10s +''' + self.assertMultiLineEqual(config, expected) + + def test_config_simple_app_vhost_with_path_and_auth(self): + apps = dict() + groups = ['external'] + bind_http_https = True + ssl_certs = "" + templater = marathon_lb.ConfigTemplater() + + healthCheck = { + "path": "/", + "protocol": "HTTP", + "portIndex": 0, + "gracePeriodSeconds": 10, + "intervalSeconds": 2, + "timeoutSeconds": 10, + "maxConsecutiveFailures": 10, + "ignoreHttp1xx": False + } + app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) + app.hostname = "test.example.com" + app.path = '/some/path' + app.groups = ['external'] + app.authRealm = "realm" + app.authUser = "testuser" + app.authPasswd = "testpasswd" + app.add_backend("1.1.1.1", 1024, False) + apps = [app] + + config = marathon_lb.config(apps, groups, bind_http_https, + ssl_certs, templater) + expected = self.base_config + ''' +userlist user_nginx_10000 + user testuser password testpasswd + +frontend marathon_http_in + bind *:80 + mode http + acl host_test_example_com_nginx hdr(host) -i test.example.com + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + acl path_nginx_10000 path_beg /some/path + http-request auth realm "realm" if host_test_example_com_nginx \ +path_nginx_10000 !auth_test_example_com_nginx + use_backend nginx_10000 if host_test_example_com_nginx path_nginx_10000 + +frontend marathon_http_appid_in + bind *:9091 + mode http + acl app__nginx hdr(x-marathon-app-id) -i /nginx + use_backend nginx_10000 if app__nginx + +frontend marathon_https_in + bind *:443 ssl crt /etc/ssl/mesosphere.com.pem + mode http + acl path_nginx_10000 path_beg /some/path + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test.example.com } \ +path_nginx_10000 !auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test.example.com } path_nginx_10000 + +frontend nginx_10000 + bind *:10000 + mode http + use_backend nginx_10000 + +backend nginx_10000 + balance roundrobin + mode http + option forwardfor + http-request set-header X-Forwarded-Port %[dst_port] + http-request add-header X-Forwarded-Proto https if { ssl_fc } + option httpchk GET / + timeout check 10s + server 1_1_1_1_1024 1.1.1.1:1024 check inter 2s fall 11 +''' + self.assertMultiLineEqual(config, expected) + + def test_config_simple_app_multiple_vhost_with_path_and_auth(self): + apps = dict() + groups = ['external'] + bind_http_https = True + ssl_certs = "" + templater = marathon_lb.ConfigTemplater() + + healthCheck = { + "path": "/", + "protocol": "HTTP", + "portIndex": 0, + "gracePeriodSeconds": 10, + "intervalSeconds": 2, + "timeoutSeconds": 10, + "maxConsecutiveFailures": 10, + "ignoreHttp1xx": False + } + app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) + app.hostname = "test.example.com,test" + app.path = '/some/path' + app.groups = ['external'] + app.authRealm = "realm" + app.authUser = "testuser" + app.authPasswd = "testpasswd" + apps = [app] + + config = marathon_lb.config(apps, groups, bind_http_https, + ssl_certs, templater) + expected = self.base_config + ''' +userlist user_nginx_10000 + user testuser password testpasswd + +frontend marathon_http_in + bind *:80 + mode http + acl path_nginx_10000 path_beg /some/path + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + acl host_test_example_com_nginx hdr(host) -i test.example.com + acl host_test_example_com_nginx hdr(host) -i test + http-request auth realm "realm" if host_test_example_com_nginx \ +path_nginx_10000 !auth_test_example_com_nginx + use_backend nginx_10000 if host_test_example_com_nginx path_nginx_10000 + +frontend marathon_http_appid_in + bind *:9091 + mode http + acl app__nginx hdr(x-marathon-app-id) -i /nginx + use_backend nginx_10000 if app__nginx + +frontend marathon_https_in + bind *:443 ssl crt /etc/ssl/mesosphere.com.pem + mode http + acl path_nginx_10000 path_beg /some/path + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test.example.com } \ +path_nginx_10000 !auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test.example.com } ''' + \ + '''path_nginx_10000 + acl auth_test_example_com_nginx http_auth(user_nginx_10000) + http-request auth realm "realm" if { ssl_fc_sni test } \ +path_nginx_10000 !auth_test_example_com_nginx + use_backend nginx_10000 if { ssl_fc_sni test } path_nginx_10000 + +frontend nginx_10000 + bind *:10000 + mode http + use_backend nginx_10000 + backend nginx_10000 balance roundrobin mode http From 15e7a254cf02ef0165893bd135dd996b0c577fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Tue, 19 Apr 2016 15:07:30 +0200 Subject: [PATCH 03/11] add haproxy acme plugin --- Longhelp.md | 3 ++ README.md | 4 +- acme-http01-webroot.lua | 107 ++++++++++++++++++++++++++++++++++++++++ config.py | 3 ++ 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 acme-http01-webroot.lua diff --git a/Longhelp.md b/Longhelp.md index 5194530c..33caa954 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -368,6 +368,7 @@ global server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua + lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -721,6 +722,8 @@ all virtual hosts as defined by the `HAPROXY_{n}_VHOST` label. frontend marathon_http_in bind *:80 mode http + acl url_acme_http01 path_beg /.well-known/acme-challenge/ + http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ``` ## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY` *Overridable* diff --git a/README.md b/README.md index 353f8b73..a6507aa9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Marathon-lb is a tool for managing HAProxy, by consuming [Marathon's](https://gi * **Stateless design**: no direct dependency on any third-party state store like ZooKeeper or etcd (_except through Marathon_) * **Real-time LB updates**, via [Marathon's event bus](https://mesosphere.github.io/marathon/docs/event-bus.html) * Support for Marathon's **health checks** - * **Multi-cert TLS/SSL** support + * **Multi-cert TLS/SSL** and **acme** support * Per-service **HAProxy templates** * DCOS integration * Automated Docker image builds ([mesosphere/marathon-lb](https://hub.docker.com/r/mesosphere/marathon-lb)) @@ -264,4 +264,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` \ No newline at end of file + ``` diff --git a/acme-http01-webroot.lua b/acme-http01-webroot.lua new file mode 100644 index 00000000..f45445da --- /dev/null +++ b/acme-http01-webroot.lua @@ -0,0 +1,107 @@ +-- ACME http-01 domain validation plugin for Haproxy 1.6+ +-- copyright (C) 2015 Jan Broer +-- +-- usage: +-- +-- 1) copy acme-webroot.lua in your haproxy config dir +-- +-- 2) Invoke the plugin by adding in the 'global' section of haproxy.cfg: +-- +-- lua-load /etc/haproxy/acme-webroot.lua +-- +-- 3) insert these two lines in every http frontend that is +-- serving domains for which you want to create certificates: +-- +-- acl url_acme_http01 path_beg /.well-known/acme-challenge/ +-- http-request use-service lua.acme-http01 if METH_GET url_acme_http01 +-- +-- 4) reload haproxy +-- +-- 5) create a certificate: +-- +-- ./letsencrypt-auto certonly --text --webroot --webroot-path /var/tmp -d blah.example.com --renew-by-default --agree-tos --email my@email.com +-- + +acme = {} +acme.version = "0.1.1" + +-- +-- Configuration +-- +-- When HAProxy is *not* configured with the 'chroot' option you must set an absolute path here and pass +-- that as 'webroot-path' to the letsencrypt client + +acme.conf = { + ["non_chroot_webroot"] = "" +} + +-- +-- Startup +-- +acme.startup = function() + core.Info("[acme] http-01 plugin v" .. acme.version); +end + +-- +-- ACME http-01 validation endpoint +-- +acme.http01 = function(applet) + local response = "" + local reqPath = applet.path + local src = applet.sf:src() + local token = reqPath:match( ".+/(.*)$" ) + + if token then + token = sanitizeToken(token) + end + + if (token == nil or token == '') then + response = "bad request\n" + applet:set_status(400) + core.Warning("[acme] malformed request (client-ip: " .. tostring(src) .. ")") + else + auth = getKeyAuth(token) + if (auth:len() >= 1) then + response = auth .. "\n" + applet:set_status(200) + core.Info("[acme] served http-01 token: " .. token .. " (client-ip: " .. tostring(src) .. ")") + else + response = "resource not found\n" + applet:set_status(404) + core.Warning("[acme] http-01 token not found: " .. token .. " (client-ip: " .. tostring(src) .. ")") + end + end + + applet:add_header("Server", "haproxy/acme-http01-authenticator") + applet:add_header("Content-Length", string.len(response)) + applet:add_header("Content-Type", "text/plain") + applet:start_response() + applet:send(response) +end + +-- +-- strip chars that are not in the URL-safe Base64 alphabet +-- see https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md +-- +function sanitizeToken(token) + _strip="[^%a%d%+%-%_=]" + token = token:gsub(_strip,'') + return token +end + +-- +-- get key auth from token file +-- +function getKeyAuth(token) + local keyAuth = "" + local path = acme.conf.non_chroot_webroot .. "/.well-known/acme-challenge/" .. token + local f = io.open(path, "rb") + if f ~= nil then + keyAuth = f:read("*all") + f:close() + end + return keyAuth +end + +core.register_init(acme.startup) +core.register_service("acme-http01", "http", acme.http01) \ No newline at end of file diff --git a/config.py b/config.py index 1c3a247f..5286055c 100644 --- a/config.py +++ b/config.py @@ -59,6 +59,7 @@ def load(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua + lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -110,6 +111,8 @@ def load(self): frontend marathon_http_in bind *:80 mode http + acl url_acme_http01 path_beg /.well-known/acme-challenge/ + http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ''', overridable=False, description='''\ From f39bbc12cd76341f1c9c09be060d81fa83c854ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Wed, 20 Apr 2016 11:49:12 +0200 Subject: [PATCH 04/11] fix some missing/extra labels --- Longhelp.md | 7 ------- config.py | 9 ++++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index 33caa954..5010003e 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -935,13 +935,6 @@ it falls back to default `HAPROXY_GROUP` and gets associated with Load balancers with the group '*' will collect all groups. -## `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH` - *per service port* - -Specified as `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH`. - - - ## `HAPROXY_{n}_MODE` *per service port* diff --git a/config.py b/config.py index 5286055c..d44e62eb 100644 --- a/config.py +++ b/config.py @@ -1296,9 +1296,6 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH', func=set_label, description='')) -labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH', - func=set_label, - description='')) labels.append(Label(name='HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH', func=set_label, description='')) @@ -1311,9 +1308,15 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='HTTPS_FRONTEND_ACL', func=set_label, description='')) +labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_PATH', func=set_label, description='')) +labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH', + func=set_label, + description='')) labels.append(Label(name='BACKEND_HTTP_OPTIONS', func=set_label, description='')) From 2159226ee18907dbe7343c73b55811b1cd878cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Wed, 20 Apr 2016 11:49:12 +0200 Subject: [PATCH 05/11] Revert "add haproxy acme plugin" This reverts commit 15e7a254cf02ef0165893bd135dd996b0c577fd7. --- Longhelp.md | 3 -- README.md | 4 +- acme-http01-webroot.lua | 107 ---------------------------------------- config.py | 3 -- 4 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 acme-http01-webroot.lua diff --git a/Longhelp.md b/Longhelp.md index 5010003e..79645f96 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -368,7 +368,6 @@ global server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -722,8 +721,6 @@ all virtual hosts as defined by the `HAPROXY_{n}_VHOST` label. frontend marathon_http_in bind *:80 mode http - acl url_acme_http01 path_beg /.well-known/acme-challenge/ - http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ``` ## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY` *Overridable* diff --git a/README.md b/README.md index a6507aa9..353f8b73 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Marathon-lb is a tool for managing HAProxy, by consuming [Marathon's](https://gi * **Stateless design**: no direct dependency on any third-party state store like ZooKeeper or etcd (_except through Marathon_) * **Real-time LB updates**, via [Marathon's event bus](https://mesosphere.github.io/marathon/docs/event-bus.html) * Support for Marathon's **health checks** - * **Multi-cert TLS/SSL** and **acme** support + * **Multi-cert TLS/SSL** support * Per-service **HAProxy templates** * DCOS integration * Automated Docker image builds ([mesosphere/marathon-lb](https://hub.docker.com/r/mesosphere/marathon-lb)) @@ -264,4 +264,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` + ``` \ No newline at end of file diff --git a/acme-http01-webroot.lua b/acme-http01-webroot.lua deleted file mode 100644 index f45445da..00000000 --- a/acme-http01-webroot.lua +++ /dev/null @@ -1,107 +0,0 @@ --- ACME http-01 domain validation plugin for Haproxy 1.6+ --- copyright (C) 2015 Jan Broer --- --- usage: --- --- 1) copy acme-webroot.lua in your haproxy config dir --- --- 2) Invoke the plugin by adding in the 'global' section of haproxy.cfg: --- --- lua-load /etc/haproxy/acme-webroot.lua --- --- 3) insert these two lines in every http frontend that is --- serving domains for which you want to create certificates: --- --- acl url_acme_http01 path_beg /.well-known/acme-challenge/ --- http-request use-service lua.acme-http01 if METH_GET url_acme_http01 --- --- 4) reload haproxy --- --- 5) create a certificate: --- --- ./letsencrypt-auto certonly --text --webroot --webroot-path /var/tmp -d blah.example.com --renew-by-default --agree-tos --email my@email.com --- - -acme = {} -acme.version = "0.1.1" - --- --- Configuration --- --- When HAProxy is *not* configured with the 'chroot' option you must set an absolute path here and pass --- that as 'webroot-path' to the letsencrypt client - -acme.conf = { - ["non_chroot_webroot"] = "" -} - --- --- Startup --- -acme.startup = function() - core.Info("[acme] http-01 plugin v" .. acme.version); -end - --- --- ACME http-01 validation endpoint --- -acme.http01 = function(applet) - local response = "" - local reqPath = applet.path - local src = applet.sf:src() - local token = reqPath:match( ".+/(.*)$" ) - - if token then - token = sanitizeToken(token) - end - - if (token == nil or token == '') then - response = "bad request\n" - applet:set_status(400) - core.Warning("[acme] malformed request (client-ip: " .. tostring(src) .. ")") - else - auth = getKeyAuth(token) - if (auth:len() >= 1) then - response = auth .. "\n" - applet:set_status(200) - core.Info("[acme] served http-01 token: " .. token .. " (client-ip: " .. tostring(src) .. ")") - else - response = "resource not found\n" - applet:set_status(404) - core.Warning("[acme] http-01 token not found: " .. token .. " (client-ip: " .. tostring(src) .. ")") - end - end - - applet:add_header("Server", "haproxy/acme-http01-authenticator") - applet:add_header("Content-Length", string.len(response)) - applet:add_header("Content-Type", "text/plain") - applet:start_response() - applet:send(response) -end - --- --- strip chars that are not in the URL-safe Base64 alphabet --- see https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md --- -function sanitizeToken(token) - _strip="[^%a%d%+%-%_=]" - token = token:gsub(_strip,'') - return token -end - --- --- get key auth from token file --- -function getKeyAuth(token) - local keyAuth = "" - local path = acme.conf.non_chroot_webroot .. "/.well-known/acme-challenge/" .. token - local f = io.open(path, "rb") - if f ~= nil then - keyAuth = f:read("*all") - f:close() - end - return keyAuth -end - -core.register_init(acme.startup) -core.register_service("acme-http01", "http", acme.http01) \ No newline at end of file diff --git a/config.py b/config.py index d44e62eb..a562da19 100644 --- a/config.py +++ b/config.py @@ -59,7 +59,6 @@ def load(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -111,8 +110,6 @@ def load(self): frontend marathon_http_in bind *:80 mode http - acl url_acme_http01 path_beg /.well-known/acme-challenge/ - http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ''', overridable=False, description='''\ From 76ab7ea06cef82ff0bb0846fd75c33c564267700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Thu, 7 Apr 2016 00:20:37 +0200 Subject: [PATCH 06/11] upstream merge --- Longhelp.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Longhelp.md b/Longhelp.md index 5194530c..ee31ac25 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -342,6 +342,19 @@ frontend {backend} bind {bindAddr}:{servicePort}{sslCert}{bindOptions} mode {mode} ``` + +## `HAPROXY_USERLIST_HEAD` + *Overridable* + +Specified as `HAPROXY_USERLIST_HEAD` template or with label `HAPROXY_{n}_USERLIST_HEAD`. + +Defines the userlist for basic http auth to for this frontend. + + +**Default template for `HAPROXY_USERLIST_HEAD`:** +``` +userlist user_{backend}
 user {user} password {passwd} +``` ## `HAPROXY_HEAD` *Global* @@ -409,6 +422,21 @@ for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. ``` use_backend {backend} if {{ ssl_fc_sni {hostname} }} ``` +## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_WITH_AUTH`. + +The ACLs that performs the SNI based hostname matching and the HTTP basic auth for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. + + +**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH`:** +``` + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} + use_backend {backend} if {{ ssl_fc_sni {hostname} }} +``` + ## `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` *Overridable* @@ -577,6 +605,22 @@ of the `HAPROXY_HTTP_FRONTEND_HEAD` acl host_{cleanedUpHostname} hdr(host) -i {hostname} use_backend {backend} if host_{cleanedUpHostname} ``` +## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` + *Overridable* + +Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH`. + +The ACL that glues a backend to the corresponding virtual host +of the `HAPROXY_HTTP_FRONTEND_HEAD` and HTTP basic auth + + +**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH`:** +``` + acl host_{cleanedUpHostname} hdr(host) -i {hostname} + acl auth_{cleanedUpHostname} http_auth(user_{backend}) + http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} + use_backend {backend} if host_{cleanedUpHostname} +``` ## `HAPROXY_HTTP_FRONTEND_ACL_ONLY` *Overridable* diff --git a/README.md b/README.md index 7a375389..2b954b68 100644 --- a/README.md +++ b/README.md @@ -303,4 +303,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` + ``` \ No newline at end of file From b201678afe310ac04b566c2c2bf6f0818cf9751b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Thu, 7 Apr 2016 10:05:12 +0200 Subject: [PATCH 07/11] add http basic auth support --- Longhelp.md | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index ee31ac25..5194530c 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -342,19 +342,6 @@ frontend {backend} bind {bindAddr}:{servicePort}{sslCert}{bindOptions} mode {mode} ``` - -## `HAPROXY_USERLIST_HEAD` - *Overridable* - -Specified as `HAPROXY_USERLIST_HEAD` template or with label `HAPROXY_{n}_USERLIST_HEAD`. - -Defines the userlist for basic http auth to for this frontend. - - -**Default template for `HAPROXY_USERLIST_HEAD`:** -``` -userlist user_{backend}
 user {user} password {passwd} -``` ## `HAPROXY_HEAD` *Global* @@ -422,21 +409,6 @@ for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. ``` use_backend {backend} if {{ ssl_fc_sni {hostname} }} ``` -## `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` - *Overridable* - -Specified as `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTPS_FRONTEND_ACL_WITH_AUTH`. - -The ACLs that performs the SNI based hostname matching and the HTTP basic auth for the `HAPROXY_HTTPS_FRONTEND_HEAD` template. - - -**Default template for `HAPROXY_HTTPS_FRONTEND_ACL_WITH_AUTH`:** -``` - acl auth_{cleanedUpHostname} http_auth(user_{backend}) - http-request auth realm "{realm}" if {{ ssl_fc_sni {hostname} }} !auth_{cleanedUpHostname} - use_backend {backend} if {{ ssl_fc_sni {hostname} }} -``` - ## `HAPROXY_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH` *Overridable* @@ -605,22 +577,6 @@ of the `HAPROXY_HTTP_FRONTEND_HEAD` acl host_{cleanedUpHostname} hdr(host) -i {hostname} use_backend {backend} if host_{cleanedUpHostname} ``` -## `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` - *Overridable* - -Specified as `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH` template or with label `HAPROXY_{n}_HTTP_FRONTEND_ACL_WITH_AUTH`. - -The ACL that glues a backend to the corresponding virtual host -of the `HAPROXY_HTTP_FRONTEND_HEAD` and HTTP basic auth - - -**Default template for `HAPROXY_HTTP_FRONTEND_ACL_WITH_AUTH`:** -``` - acl host_{cleanedUpHostname} hdr(host) -i {hostname} - acl auth_{cleanedUpHostname} http_auth(user_{backend}) - http-request auth realm "{realm}" if host_{cleanedUpHostname} !auth_{cleanedUpHostname} - use_backend {backend} if host_{cleanedUpHostname} -``` ## `HAPROXY_HTTP_FRONTEND_ACL_ONLY` *Overridable* From b0f036ff5f7c9e576bcb7de8d8a7c0683e4f3bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Tue, 19 Apr 2016 15:07:30 +0200 Subject: [PATCH 08/11] add haproxy acme plugin --- Longhelp.md | 3 ++ README.md | 4 +- acme-http01-webroot.lua | 107 ++++++++++++++++++++++++++++++++++++++++ config.py | 3 ++ 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 acme-http01-webroot.lua diff --git a/Longhelp.md b/Longhelp.md index 5194530c..33caa954 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -368,6 +368,7 @@ global server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua + lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -721,6 +722,8 @@ all virtual hosts as defined by the `HAPROXY_{n}_VHOST` label. frontend marathon_http_in bind *:80 mode http + acl url_acme_http01 path_beg /.well-known/acme-challenge/ + http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ``` ## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY` *Overridable* diff --git a/README.md b/README.md index 2b954b68..8f74d46e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ fast, efficient, battle-tested, highly available load balancer with many advance * **Highly scalable**: [can achieve line-rate](http://www.haproxy.org/10g.html) per instance, with multiple instances providing fault-tolerance and greater throughput * **Real-time LB updates**, via [Marathon's event bus](https://mesosphere.github.io/marathon/docs/event-bus.html) * Support for Marathon's **health checks** - * **Multi-cert TLS/SSL** support + * **Multi-cert TLS/SSL** and **acme** support * Per-service **HAProxy templates** * DC/OS integration * Automated Docker image builds ([mesosphere/marathon-lb](https://hub.docker.com/r/mesosphere/marathon-lb)) @@ -303,4 +303,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` \ No newline at end of file + ``` diff --git a/acme-http01-webroot.lua b/acme-http01-webroot.lua new file mode 100644 index 00000000..f45445da --- /dev/null +++ b/acme-http01-webroot.lua @@ -0,0 +1,107 @@ +-- ACME http-01 domain validation plugin for Haproxy 1.6+ +-- copyright (C) 2015 Jan Broer +-- +-- usage: +-- +-- 1) copy acme-webroot.lua in your haproxy config dir +-- +-- 2) Invoke the plugin by adding in the 'global' section of haproxy.cfg: +-- +-- lua-load /etc/haproxy/acme-webroot.lua +-- +-- 3) insert these two lines in every http frontend that is +-- serving domains for which you want to create certificates: +-- +-- acl url_acme_http01 path_beg /.well-known/acme-challenge/ +-- http-request use-service lua.acme-http01 if METH_GET url_acme_http01 +-- +-- 4) reload haproxy +-- +-- 5) create a certificate: +-- +-- ./letsencrypt-auto certonly --text --webroot --webroot-path /var/tmp -d blah.example.com --renew-by-default --agree-tos --email my@email.com +-- + +acme = {} +acme.version = "0.1.1" + +-- +-- Configuration +-- +-- When HAProxy is *not* configured with the 'chroot' option you must set an absolute path here and pass +-- that as 'webroot-path' to the letsencrypt client + +acme.conf = { + ["non_chroot_webroot"] = "" +} + +-- +-- Startup +-- +acme.startup = function() + core.Info("[acme] http-01 plugin v" .. acme.version); +end + +-- +-- ACME http-01 validation endpoint +-- +acme.http01 = function(applet) + local response = "" + local reqPath = applet.path + local src = applet.sf:src() + local token = reqPath:match( ".+/(.*)$" ) + + if token then + token = sanitizeToken(token) + end + + if (token == nil or token == '') then + response = "bad request\n" + applet:set_status(400) + core.Warning("[acme] malformed request (client-ip: " .. tostring(src) .. ")") + else + auth = getKeyAuth(token) + if (auth:len() >= 1) then + response = auth .. "\n" + applet:set_status(200) + core.Info("[acme] served http-01 token: " .. token .. " (client-ip: " .. tostring(src) .. ")") + else + response = "resource not found\n" + applet:set_status(404) + core.Warning("[acme] http-01 token not found: " .. token .. " (client-ip: " .. tostring(src) .. ")") + end + end + + applet:add_header("Server", "haproxy/acme-http01-authenticator") + applet:add_header("Content-Length", string.len(response)) + applet:add_header("Content-Type", "text/plain") + applet:start_response() + applet:send(response) +end + +-- +-- strip chars that are not in the URL-safe Base64 alphabet +-- see https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md +-- +function sanitizeToken(token) + _strip="[^%a%d%+%-%_=]" + token = token:gsub(_strip,'') + return token +end + +-- +-- get key auth from token file +-- +function getKeyAuth(token) + local keyAuth = "" + local path = acme.conf.non_chroot_webroot .. "/.well-known/acme-challenge/" .. token + local f = io.open(path, "rb") + if f ~= nil then + keyAuth = f:read("*all") + f:close() + end + return keyAuth +end + +core.register_init(acme.startup) +core.register_service("acme-http01", "http", acme.http01) \ No newline at end of file diff --git a/config.py b/config.py index 889fd1ab..c5f5bbba 100644 --- a/config.py +++ b/config.py @@ -59,6 +59,7 @@ def load(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua + lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -110,6 +111,8 @@ def load(self): frontend marathon_http_in bind *:80 mode http + acl url_acme_http01 path_beg /.well-known/acme-challenge/ + http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ''', overridable=False, description='''\ From 804339b2d20792aae34dc6af37a17f6f964560d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Wed, 20 Apr 2016 11:49:12 +0200 Subject: [PATCH 09/11] fix some missing/extra labels --- Longhelp.md | 7 ------- config.py | 9 ++++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index 33caa954..5010003e 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -935,13 +935,6 @@ it falls back to default `HAPROXY_GROUP` and gets associated with Load balancers with the group '*' will collect all groups. -## `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH` - *per service port* - -Specified as `HAPROXY_{n}_HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH`. - - - ## `HAPROXY_{n}_MODE` *per service port* diff --git a/config.py b/config.py index c5f5bbba..3cc967da 100644 --- a/config.py +++ b/config.py @@ -1296,9 +1296,6 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH', func=set_label, description='')) -labels.append(Label(name='HTTPS_FRONTEND_ACL_ONLY_WITH_PATH_AND_AUTH', - func=set_label, - description='')) labels.append(Label(name='HTTP_FRONTEND_ROUTING_ONLY_WITH_PATH', func=set_label, description='')) @@ -1311,9 +1308,15 @@ def __init__(self, name, func, description, perServicePort=True): labels.append(Label(name='HTTPS_FRONTEND_ACL', func=set_label, description='')) +labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_AUTH', + func=set_label, + description='')) labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_PATH', func=set_label, description='')) +labels.append(Label(name='HTTPS_FRONTEND_ACL_WITH_AUTH_AND_PATH', + func=set_label, + description='')) labels.append(Label(name='BACKEND_HTTP_OPTIONS', func=set_label, description='')) From 3d74e607227d244ebf9641934709c37f1ed22e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Wed, 20 Apr 2016 11:49:12 +0200 Subject: [PATCH 10/11] Revert "add haproxy acme plugin" This reverts commit 15e7a254cf02ef0165893bd135dd996b0c577fd7. --- Longhelp.md | 3 -- README.md | 4 +- acme-http01-webroot.lua | 107 ---------------------------------------- config.py | 3 -- 4 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 acme-http01-webroot.lua diff --git a/Longhelp.md b/Longhelp.md index 5010003e..79645f96 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -368,7 +368,6 @@ global server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -722,8 +721,6 @@ all virtual hosts as defined by the `HAPROXY_{n}_VHOST` label. frontend marathon_http_in bind *:80 mode http - acl url_acme_http01 path_beg /.well-known/acme-challenge/ - http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ``` ## `HAPROXY_HTTP_FRONTEND_ROUTING_ONLY` *Overridable* diff --git a/README.md b/README.md index 8f74d46e..2b954b68 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ fast, efficient, battle-tested, highly available load balancer with many advance * **Highly scalable**: [can achieve line-rate](http://www.haproxy.org/10g.html) per instance, with multiple instances providing fault-tolerance and greater throughput * **Real-time LB updates**, via [Marathon's event bus](https://mesosphere.github.io/marathon/docs/event-bus.html) * Support for Marathon's **health checks** - * **Multi-cert TLS/SSL** and **acme** support + * **Multi-cert TLS/SSL** support * Per-service **HAProxy templates** * DC/OS integration * Automated Docker image builds ([mesosphere/marathon-lb](https://hub.docker.com/r/mesosphere/marathon-lb)) @@ -303,4 +303,4 @@ PRs are welcome, but here are a few general guidelines: - Use the pre-commit hook to automatically generate docs: ``` bash /path/to/marathon-lb/scripts/install-git-hooks.sh - ``` + ``` \ No newline at end of file diff --git a/acme-http01-webroot.lua b/acme-http01-webroot.lua deleted file mode 100644 index f45445da..00000000 --- a/acme-http01-webroot.lua +++ /dev/null @@ -1,107 +0,0 @@ --- ACME http-01 domain validation plugin for Haproxy 1.6+ --- copyright (C) 2015 Jan Broer --- --- usage: --- --- 1) copy acme-webroot.lua in your haproxy config dir --- --- 2) Invoke the plugin by adding in the 'global' section of haproxy.cfg: --- --- lua-load /etc/haproxy/acme-webroot.lua --- --- 3) insert these two lines in every http frontend that is --- serving domains for which you want to create certificates: --- --- acl url_acme_http01 path_beg /.well-known/acme-challenge/ --- http-request use-service lua.acme-http01 if METH_GET url_acme_http01 --- --- 4) reload haproxy --- --- 5) create a certificate: --- --- ./letsencrypt-auto certonly --text --webroot --webroot-path /var/tmp -d blah.example.com --renew-by-default --agree-tos --email my@email.com --- - -acme = {} -acme.version = "0.1.1" - --- --- Configuration --- --- When HAProxy is *not* configured with the 'chroot' option you must set an absolute path here and pass --- that as 'webroot-path' to the letsencrypt client - -acme.conf = { - ["non_chroot_webroot"] = "" -} - --- --- Startup --- -acme.startup = function() - core.Info("[acme] http-01 plugin v" .. acme.version); -end - --- --- ACME http-01 validation endpoint --- -acme.http01 = function(applet) - local response = "" - local reqPath = applet.path - local src = applet.sf:src() - local token = reqPath:match( ".+/(.*)$" ) - - if token then - token = sanitizeToken(token) - end - - if (token == nil or token == '') then - response = "bad request\n" - applet:set_status(400) - core.Warning("[acme] malformed request (client-ip: " .. tostring(src) .. ")") - else - auth = getKeyAuth(token) - if (auth:len() >= 1) then - response = auth .. "\n" - applet:set_status(200) - core.Info("[acme] served http-01 token: " .. token .. " (client-ip: " .. tostring(src) .. ")") - else - response = "resource not found\n" - applet:set_status(404) - core.Warning("[acme] http-01 token not found: " .. token .. " (client-ip: " .. tostring(src) .. ")") - end - end - - applet:add_header("Server", "haproxy/acme-http01-authenticator") - applet:add_header("Content-Length", string.len(response)) - applet:add_header("Content-Type", "text/plain") - applet:start_response() - applet:send(response) -end - --- --- strip chars that are not in the URL-safe Base64 alphabet --- see https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md --- -function sanitizeToken(token) - _strip="[^%a%d%+%-%_=]" - token = token:gsub(_strip,'') - return token -end - --- --- get key auth from token file --- -function getKeyAuth(token) - local keyAuth = "" - local path = acme.conf.non_chroot_webroot .. "/.well-known/acme-challenge/" .. token - local f = io.open(path, "rb") - if f ~= nil then - keyAuth = f:read("*all") - f:close() - end - return keyAuth -end - -core.register_init(acme.startup) -core.register_service("acme-http01", "http", acme.http01) \ No newline at end of file diff --git a/config.py b/config.py index 3cc967da..eb7ce670 100644 --- a/config.py +++ b/config.py @@ -59,7 +59,6 @@ def load(self): server-state-base /var/state/haproxy/ lua-load /marathon-lb/getpids.lua lua-load /marathon-lb/getconfig.lua - lua-load /marathon-lb/acme-http01-webroot.lua defaults load-server-state-from-file global log global @@ -111,8 +110,6 @@ def load(self): frontend marathon_http_in bind *:80 mode http - acl url_acme_http01 path_beg /.well-known/acme-challenge/ - http-request use-service lua.acme-http01 if METH_GET url_acme_http01 ''', overridable=False, description='''\ From 5af4b075b6c656617de66a5c84aa9bd0b5118321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Cottin?= Date: Wed, 21 Sep 2016 01:39:16 +0200 Subject: [PATCH 11/11] mesos http healthcheck does not support ignoreHttp1xx, remove it. --- Longhelp.md | 2 - config.py | 2 - marathon_lb.py | 1 - tests/test_marathon_lb.py | 84 +++++++++++++-------------------------- tests/zdd_app_blue.json | 3 +- tests/zdd_apps.json | 6 +-- 6 files changed, 31 insertions(+), 67 deletions(-) diff --git a/Longhelp.md b/Longhelp.md index 06af3f3e..8e6eb56d 100644 --- a/Longhelp.md +++ b/Longhelp.md @@ -156,7 +156,6 @@ Parameters of the first health check for this service are exposed as: * healthCheckPath * healthCheckTimeoutSeconds * healthCheckIntervalSeconds - * healthCheckIgnoreHttp1xx * healthCheckGracePeriodSeconds * healthCheckMaxConsecutiveFailures * healthCheckFalls is set to healthCheckMaxConsecutiveFailures + 1 @@ -229,7 +228,6 @@ Parameters of the first health check for this service are exposed as: * healthCheckPath * healthCheckTimeoutSeconds * healthCheckIntervalSeconds - * healthCheckIgnoreHttp1xx * healthCheckGracePeriodSeconds * healthCheckMaxConsecutiveFailures * healthCheckFalls is set to healthCheckMaxConsecutiveFailures + 1 diff --git a/config.py b/config.py index 0bef6768..76e9cbfb 100644 --- a/config.py +++ b/config.py @@ -578,7 +578,6 @@ def load(self): * healthCheckPath * healthCheckTimeoutSeconds * healthCheckIntervalSeconds - * healthCheckIgnoreHttp1xx * healthCheckGracePeriodSeconds * healthCheckMaxConsecutiveFailures * healthCheckFalls is set to healthCheckMaxConsecutiveFailures + 1 @@ -655,7 +654,6 @@ def load(self): * healthCheckPath * healthCheckTimeoutSeconds * healthCheckIntervalSeconds - * healthCheckIgnoreHttp1xx * healthCheckGracePeriodSeconds * healthCheckMaxConsecutiveFailures * healthCheckFalls is set to healthCheckMaxConsecutiveFailures + 1 diff --git a/marathon_lb.py b/marathon_lb.py index df0425b3..664eb486 100755 --- a/marathon_lb.py +++ b/marathon_lb.py @@ -334,7 +334,6 @@ def _get_health_check_options(template, health_check, health_check_port): healthCheckPath=health_check.get('path', '/'), healthCheckTimeoutSeconds=health_check['timeoutSeconds'], healthCheckIntervalSeconds=health_check['intervalSeconds'], - healthCheckIgnoreHttp1xx=health_check['ignoreHttp1xx'], healthCheckGracePeriodSeconds=health_check['gracePeriodSeconds'], healthCheckMaxConsecutiveFailures=health_check[ 'maxConsecutiveFailures'], diff --git a/tests/test_marathon_lb.py b/tests/test_marathon_lb.py index 39a6bb30..6abd0cbf 100644 --- a/tests/test_marathon_lb.py +++ b/tests/test_marathon_lb.py @@ -164,8 +164,7 @@ def test_config_simple_app(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -223,8 +222,7 @@ def test_config_healthcheck_command(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -272,8 +270,7 @@ def test_config_simple_app_vhost(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -332,8 +329,7 @@ def test_config_simple_app_multiple_vhost(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -394,8 +390,7 @@ def test_config_simple_app_vhost_and_redirect(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -455,8 +450,7 @@ def test_config_simple_app_multiple_vhost_and_redirect(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -518,8 +512,7 @@ def test_config_simple_app_vhost_with_auth(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -590,8 +583,7 @@ def test_config_simple_app_multiple_vhost_and_auth(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -667,8 +659,7 @@ def test_config_simple_app_vhost_with_path_and_auth(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -742,8 +733,7 @@ def test_config_simple_app_multiple_vhost_with_path_and_auth(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -823,8 +813,7 @@ def test_config_simple_app_vhost_with_path(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -886,8 +875,7 @@ def test_config_simple_app_multiple_vhost_with_path(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -952,8 +940,7 @@ def test_config_simple_app_vhost_with_path_and_redirect(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com" @@ -1017,8 +1004,7 @@ def test_config_simple_app_multiple_vhost_with_path_and_redirect(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -1085,8 +1071,7 @@ def test_config_simple_app_multiple_vhost_path_redirect_hsts(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -1155,8 +1140,7 @@ def test_config_simple_app_balance(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.balance = "leastconn" @@ -1268,8 +1252,7 @@ def test_config_simple_app_healthcheck_port(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -1324,8 +1307,7 @@ def test_config_simple_app_healthcheck_port_using_another_portindex(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -1399,8 +1381,7 @@ def test_config_simple_app_healthcheck_port_diff_portindex_and_group(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -1481,8 +1462,7 @@ def test_config_simple_app_healthcheck_port_portindex_out_of_range(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -1555,8 +1535,7 @@ def test_config_simple_app_tcp_healthcheck(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.groups = ['external'] @@ -1951,8 +1930,7 @@ def test_config_multi_app_multiple_vhost_with_path(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.hostname = "test.example.com,test" @@ -2101,8 +2079,7 @@ def test_config_haproxy_map(self): "gracePeriodSeconds": 15, "intervalSeconds": 3, "timeoutSeconds": 15, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app1 = marathon_lb.MarathonService('/nginx', 10000, healthCheck) @@ -2207,8 +2184,7 @@ def test_config_haproxy_map_hybrid(self): "gracePeriodSeconds": 15, "intervalSeconds": 3, "timeoutSeconds": 15, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app1 = marathon_lb.MarathonService('/nginx', 10000, healthCheck) @@ -2319,8 +2295,7 @@ def test_config_haproxy_map_hybrid_with_vhost_path(self): "gracePeriodSeconds": 15, "intervalSeconds": 3, "timeoutSeconds": 15, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app1 = marathon_lb.MarathonService('/nginx', 10000, healthCheck) @@ -2434,8 +2409,7 @@ def test_config_haproxy_map_hybrid_httptohttps_redirect(self): "gracePeriodSeconds": 15, "intervalSeconds": 3, "timeoutSeconds": 15, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app1 = marathon_lb.MarathonService('/nginx', 10000, healthCheck) @@ -2544,8 +2518,7 @@ def test_config_simple_app_proxypass_health_check(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.proxypath = "/proxy/path" @@ -2603,8 +2576,7 @@ def test_config_simple_app_revproxy_health_check(self): "gracePeriodSeconds": 10, "intervalSeconds": 2, "timeoutSeconds": 10, - "maxConsecutiveFailures": 10, - "ignoreHttp1xx": False + "maxConsecutiveFailures": 10 } app = marathon_lb.MarathonService('/nginx', 10000, healthCheck) app.revproxypath = "/proxy/path" diff --git a/tests/zdd_app_blue.json b/tests/zdd_app_blue.json index 6554d872..67bcbca6 100644 --- a/tests/zdd_app_blue.json +++ b/tests/zdd_app_blue.json @@ -60,8 +60,7 @@ "gracePeriodSeconds":15, "intervalSeconds":3, "timeoutSeconds":15, - "maxConsecutiveFailures":10, - "ignoreHttp1xx":false + "maxConsecutiveFailures":10 } ], "dependencies":[ diff --git a/tests/zdd_apps.json b/tests/zdd_apps.json index 53fac93f..ea8a625c 100644 --- a/tests/zdd_apps.json +++ b/tests/zdd_apps.json @@ -60,8 +60,7 @@ "gracePeriodSeconds":15, "intervalSeconds":3, "timeoutSeconds":15, - "maxConsecutiveFailures":10, - "ignoreHttp1xx":false + "maxConsecutiveFailures":10 } ], "dependencies":[ @@ -245,8 +244,7 @@ "gracePeriodSeconds":15, "intervalSeconds":3, "timeoutSeconds":15, - "maxConsecutiveFailures":10, - "ignoreHttp1xx":false + "maxConsecutiveFailures":10 } ], "dependencies":[