Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit 0014a80

Browse files
author
Feng Honglin
authored
v1.6.3 (#166)
* Adds option for turning off basic auth for selected services (#154) * Allow a service to be configured as failover for another service in the same backend (#120) * Added support for hot-failover mode * Updated README * Add ability to pass hashed password credentials to HAproxy. (#107) * Remove DHE-RSA-AES128-SHA from SSL_BIND_CIPHERS (#103) * fixed tests error by PRs * do not return haproxy ingress network (#160) * fix the error introduced by EXCLUDE_BASIC_AUTH * bump version
1 parent b881015 commit 0014a80

15 files changed

+162
-50
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ ENV RSYSLOG_DESTINATION=127.0.0.1 \
2121
STATS_PORT=1936 \
2222
STATS_AUTH="stats:stats" \
2323
SSL_BIND_OPTIONS=no-sslv3 \
24-
SSL_BIND_CIPHERS="ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA" \
24+
SSL_BIND_CIPHERS="ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA" \
2525
HEALTH_CHECK="check inter 2000 rise 2 fall 3" \
2626
NBPROC=1
2727

Dockerfile-dev

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM alpine:3.4
2+
3+
RUN echo PyYAML==3.11 > requirements.txt && \
4+
echo cached-property==1.2.0 >> requirements.txt && \
5+
echo docker-py==1.10.3 >> requirements.txt && \
6+
echo dockerpty==0.4.1 >> requirements.txt && \
7+
echo docopt==0.6.1 >> requirements.txt && \
8+
venum34==1.0.4 >> requirements.txt && \
9+
echo jsonschema==2.5.1 >> requirements.txt && \
10+
vtexttable==0.8.4 >> requirements.txt && \
11+
echo future==0.15.0 >> requirements.txt && \
12+
echo requests==2.7.0 >> requirements.txt && \
13+
echo six==1.9.0 >> requirements.txt && \
14+
echo websocket-client==0.37.0 >> requirements.txt && \
15+
echo docker-compose==1.6.0 >> requirements.txt && \
16+
echo python-dockercloud==1.0.5 >> requirements.txt && \
17+
echo gevent==1.1.1 >> requirements.txt
18+
19+
RUN apk update && \
20+
apk --no-cache add tini haproxy py-pip build-base python-dev ca-certificates && \
21+
pip install -r requirements.txt
22+
23+
ENV RSYSLOG_DESTINATION=127.0.0.1 \
24+
MODE=http \
25+
BALANCE=roundrobin \
26+
MAXCONN=4096 \
27+
OPTION="redispatch, httplog, dontlognull, forwardfor" \
28+
TIMEOUT="connect 5000, client 50000, server 50000" \
29+
STATS_PORT=1936 \
30+
STATS_AUTH="stats:stats" \
31+
SSL_BIND_OPTIONS=no-sslv3 \
32+
SSL_BIND_CIPHERS="ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:DHE-DSS-AES128-SHA:DES-CBC3-SHA" \
33+
HEALTH_CHECK="check inter 2000 rise 2 fall 3" \
34+
NBPROC=1
35+
36+
COPY . /haproxy-src
37+
RUN cp /haproxy-src/reload.sh /reload.sh && \
38+
cd /haproxy-src && \
39+
pip install .
40+
41+
42+
EXPOSE 80 443 1936
43+
ENTRYPOINT ["/sbin/tini", "--"]
44+
CMD ["dockercloud-haproxy"]

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ Similar to using legacy links, here list some differences that you need to notic
156156
Once the stack is up, you can scale the web service using `docker-compose scale web=3`. dockercloud/haproxy will automatically reload its configuration.
157157

158158
#### Running with Docker Compose v2 and Swarm (using envvar)
159-
When using links like previous section, the Docker Swarm scheduler can be too restrictive.
160-
Even with overlay network, swarm (As of 1.1.0) will attempt to schedule haproxy on the same node as the linked service due to legacy links behavior.
159+
When using links like previous section, the Docker Swarm scheduler can be too restrictive.
160+
Even with overlay network, swarm (As of 1.1.0) will attempt to schedule haproxy on the same node as the linked service due to legacy links behavior.
161161
This can cause unwanted scheduling patterns or errors such as "Unable to find a node fulfilling all dependencies..."
162162

163163
Since Compose V2 allows discovery through the service names, Dockercloud haproxy only needs the links to indentify which service should be load balanced.
@@ -214,6 +214,7 @@ Settings in this part is immutable, you have to redeploy HAProxy service to make
214214
|FORCE_DEFAULT_BACKEND| True | set the default_service as a default backend. This is useful when you have more than one backend and you don't want your default_service as a default backend
215215
|HEALTH_CHECK|check|set health check on each backend route, possible value: "check inter 2000 rise 2 fall 3". See:[HAProxy:check](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.2-check)|
216216
|HTTP_BASIC_AUTH| |a comma-separated list of credentials(`<user>:<pass>`) for HTTP basic auth, which applies to all the backend routes. To escape comma, use `\,`. *Attention:* DO NOT rely on this for authentication in production|
217+
|HTTP_BASIC_AUTH_SECURE| |a comma-separated list of credentials(`<user>:<encrypted-pass>`) for HTTP basic auth, which applies to all the backend routes. To escape comma, use `\,`. See:[HAProxy:user](https://cbonte.github.io/haproxy-dconv/1.5/configuration.html#3.4-user) *Attention:* DO NOT rely on this for authentication in production|
217218
|MAXCONN|4096|sets the maximum per-process number of concurrent connections.|
218219
|MODE|http|mode of load balancing for HAProxy. Possible values include: `http`, `tcp`, `health`|
219220
|MONITOR_PORT| |the port number where monitor_uri should be added to. Use together with `MONTIOR_URI`. Possible value: `80`|
@@ -238,9 +239,11 @@ Settings here can overwrite the settings in HAProxy, which are only applied to t
238239
|BALANCE|load balancing algorithm to use. Possible values include: `roundrobin`, `static-rr`, `source`, `leastconn`. See:[HAProxy:balance](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-balance)|
239240
|COOKIE|sticky session option. Possible value `SRV insert indirect nocache`. See:[HAProxy:cookie](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-cookie)|
240241
|DEFAULT_SSL_CERT|similar to SSL_CERT, but stores the pem file at `/certs/cert0.pem` as the default ssl certs. If multiple `DEFAULT_SSL_CERT` are specified in linked services and HAProxy, the behavior is undefined|
241-
|EXCLUDE_PORTS|comma separated port numbers(e.g. 3306, 3307). By default, HAProxy will add all the ports exposed by the application services to the backend routes. You can exclude the ports that you don't want to be routed, like database port|
242+
|EXCLUDE_BASIC_AUTH|if set, the application by the application services to the backend routes. You can exclude the ports that you don't want to be routed, like database port|
243+
|EXCLUDE_PORTS|if set(any value) and `HTTP_BASIC_AUTH` global setting is set, no basic auth will be applied to this service.|
242244
|EXTRA_ROUTE_SETTINGS|a string which is append to the each backend route after the health check,possible value: "send-proxy"|
243245
|EXTRA_SETTINGS|comma-separated string of extra settings, and each part will be appended to either related backend section or listen session in the configuration file. To escape comma, use `\,`. Possible value: `balance source`|
246+
|FAILOVER|if set(any value), it configures this service to be run as HAProxy `backup` for other configured service(s) in this backend|
244247
|FORCE_SSL|if set(any value) together with ssl termination enabled. HAProxy will redirect HTTP request to HTTPS request.
245248
|GZIP_COMPRESSION_TYPE|enable gzip compression. The value of this envvar is a list of MIME types that will be compressed. Some possible values: `text/html text/plain text/css application/javascript`. See:[HAProxy:compression](http://cbonte.github.io/haproxy-dconv/configuration-1.5.html#4-compression)|
246249
|HEALTH_CHECK|set health check on each backend route, possible value: "check inter 2000 rise 2 fall 3". See:[HAProxy:check](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.2-check)|

haproxy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.6.2"
1+
__version__ = "1.6.3"

haproxy/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def parse_additional_backend_settings(envvars):
7070
HAPROXY_SERVICE_URI = os.getenv("DOCKERCLOUD_SERVICE_API_URI")
7171
HEALTH_CHECK = os.getenv("HEALTH_CHECK", "check inter 2000 rise 2 fall 3")
7272
HTTP_BASIC_AUTH = os.getenv("HTTP_BASIC_AUTH")
73+
HTTP_BASIC_AUTH_SECURE = os.getenv("HTTP_BASIC_AUTH_SECURE")
7374
MAXCONN = os.getenv("MAXCONN", "4096")
7475
MODE = os.getenv("MODE", "http")
7576
MONITOR_PORT = os.getenv("MONITOR_PORT")

haproxy/haproxycfg.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def update(self):
163163
cfg_dict.update(self._config_global_section())
164164
cfg_dict.update(self._config_defaults_section())
165165
cfg_dict.update(self._config_stats_section())
166-
cfg_dict.update(self._config_userlist_section(HTTP_BASIC_AUTH))
166+
cfg_dict.update(self._config_userlist_section(HTTP_BASIC_AUTH, HTTP_BASIC_AUTH_SECURE))
167167
cfg_dict.update(self._config_tcp_sections())
168168
cfg_dict.update(self._config_frontend_sections())
169169
cfg_dict.update(self._config_backend_sections())
@@ -290,21 +290,27 @@ def _config_defaults_section():
290290
return cfg
291291

292292
@staticmethod
293-
def _config_userlist_section(basic_auth):
294-
cfg = OrderedDict()
295-
if basic_auth:
296-
auth_list = re.split(r'(?<!\\),', basic_auth)
293+
def _parse_userlist(auth_section, type):
294+
userlist = []
295+
if auth_section:
296+
auth_list = re.split(r'(?<!\\),', auth_section)
297297
userlist = []
298298
for auth in auth_list:
299299
if auth.strip():
300300
terms = auth.strip().split(":", 1)
301301
if len(terms) == 2:
302302
username = terms[0].replace("\,", ",")
303303
password = terms[1].replace("\,", ",")
304-
userlist.append("user %s insecure-password %s" % (username, password))
304+
userlist.append("user %s %s %s" % (username, type, password))
305+
return userlist
305306

306-
if userlist:
307-
cfg["userlist haproxy_userlist"] = userlist
307+
@staticmethod
308+
def _config_userlist_section(basic_auth, basic_auth_secure):
309+
cfg = OrderedDict()
310+
userlist = Haproxy._parse_userlist(basic_auth, "insecure-password") + \
311+
Haproxy._parse_userlist(basic_auth_secure, "password")
312+
if userlist:
313+
cfg["userlist haproxy_userlist"] = userlist
308314
return cfg
309315

310316
def _config_tcp_sections(self):

haproxy/helper/backend_helper.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ def get_backend_section(details, routes, vhosts, service_alias, routes_added):
1616
route_health_check = get_route_health_check(details, service_alias, HEALTH_CHECK)
1717
extra_route_settings = get_extra_route_settings(details, service_alias, EXTRA_ROUTE_SETTINGS)
1818
route_setting = " ".join([route_health_check, extra_route_settings]).strip()
19-
backend_routes = get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias)
19+
backend_routes = get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias, details)
2020
backend.extend(backend_routes)
2121

2222
return backend
2323

2424

25-
def get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias):
25+
def get_backend_routes(route_setting, is_sticky, routes, routes_added, service_alias, details):
2626
backend_routes = []
2727
for _service_alias, routes in routes.iteritems():
2828
if not service_alias or _service_alias == service_alias:
@@ -41,6 +41,9 @@ def get_backend_routes(route_setting, is_sticky, routes, routes_added, service_a
4141
if route_setting:
4242
backend_route.append(route_setting)
4343

44+
if details.get(service_alias, {}).get('failover', False):
45+
backend_route.append("backup")
46+
4447
backend_routes.append(" ".join(backend_route))
4548

4649
return sorted(backend_routes)
@@ -80,7 +83,7 @@ def get_backend_settings(details, service_alias, basic_auth):
8083
backend_settings.extend(get_hsts_max_age_setting(details, service_alias))
8184
backend_settings.extend(get_options_setting(details, service_alias))
8285
backend_settings.extend(get_extra_settings_setting(details, service_alias))
83-
backend_settings.extend(get_basic_auth_setting(basic_auth))
86+
backend_settings.extend(get_basic_auth_setting(details, basic_auth, service_alias))
8487

8588
return backend_settings, is_sticky
8689

@@ -163,9 +166,11 @@ def get_extra_settings_setting(details, service_alias):
163166
return setting
164167

165168

166-
def get_basic_auth_setting(basic_auth):
169+
def get_basic_auth_setting(details, basic_auth, service_alias):
167170
setting = []
168-
if basic_auth:
171+
exclude_basic_auth = get_service_attribute(details, "exclude_basic_auth", service_alias)
172+
173+
if basic_auth and not exclude_basic_auth:
169174
setting.append("acl need_auth http_auth(haproxy_userlist)")
170175
setting.append("http-request auth realm haproxy_basic_auth if !need_auth")
171176
return setting

haproxy/helper/swarm_mode_link_helper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ def get_swarm_mode_haproxy_id_nets(docker, haproxy_container_short_id):
1818
logger.info("Dockercloud haproxy is not running in a service in SwarmMode")
1919
return "", set()
2020

21-
haproxy_nets = set([network.get("NetworkID", "") for network in
22-
haproxy_container.get("NetworkSettings", {}).get("Networks", {}).values()])
21+
haproxy_nets = set([network.get("NetworkID", "") for name, network in
22+
haproxy_container.get("NetworkSettings", {}).get("Networks", {}).iteritems()
23+
if name != "ingress"])
2324

2425
return haproxy_service_id, haproxy_nets
2526

haproxy/parser/base_parser.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,11 @@ def parse_extra_settings(value):
188188
@staticmethod
189189
def parse_extra_route_settings(value):
190190
return value
191+
192+
@staticmethod
193+
def parse_failover(value):
194+
return value
195+
196+
@staticmethod
197+
def parse_exclude_basic_auth(value):
198+
return value

tests/test_legacy_links.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,15 @@ curl -I --resolve web-a.com:80:${DOCKER_HOST_IP} http://web-a.com 2>&1 | grep -i
413413
curl --resolve web-a.org:80:${DOCKER_HOST_IP} http://web-a.org 2>&1 | grep -iF "My hostname is web-a" > /dev/null
414414
echo
415415

416+
echo "=> Testing Basic Auth and EXCLUDE_BASIC_AUTH"
417+
rm_container web-a web-b lb
418+
docker run -d --name web-a -e HOSTNAME=web-a -e VIRTUAL_HOST=web-a.org dockercloud/hello-world
419+
docker run -d --name web-b -e HOSTNAME=web-b -e VIRTUAL_HOST=web-b.org -e EXCLUDE_BASIC_AUTH=true dockercloud/hello-world
420+
docker run -d --name lb --link web-a:web-a --link web-b:web-b -e HTTP_BASIC_AUTH=abc:abc -p 80:80 haproxy
421+
wait_for_startup http://${DOCKER_HOST_IP}
422+
curl -sSfL --resolve web-a.org:80:${DOCKER_HOST_IP} http://web-a.org 2>&1 | grep -iF "401 Unauthorized"
423+
curl -sSfL --resolve web-a.org:80:${DOCKER_HOST_IP} -u abc:abc http://web-a.org 2>&1 | grep -iF "My hostname is web-a"
424+
curl -sSfL --resolve web-b.org:80:${DOCKER_HOST_IP} http://web-b.org 2>&1 | grep -iF "My hostname is web-b"
416425
echo "=> Clean up"
417426
cleanup
418427
echo "=> Done!"

0 commit comments

Comments
 (0)