Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion charmhelpers/contrib/openstack/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,9 @@ def __call__(self):
if config('haproxy-connect-timeout'):
ctxt['haproxy_connect_timeout'] = config('haproxy-connect-timeout')

if config('haproxy-enable-proxy-protocol'):
ctxt['haproxy_enable_proxy_protocol'] = True

if config('prefer-ipv6'):
ctxt['local_host'] = 'ip6-localhost'
ctxt['haproxy_host'] = '::'
Expand Down Expand Up @@ -1150,7 +1153,8 @@ class ApacheSSLContext(OSContextGenerator):
user = group = 'root'

def enable_modules(self):
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers']
# NOTE(seyeongkim) : remoteip is for proxy protocol
cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers', 'remoteip']
check_call(cmd)

def configure_cert(self, cn=None):
Expand Down Expand Up @@ -1283,6 +1287,8 @@ def __call__(self):
ctxt['ext_ports'].append(int(ext_port))

ctxt['ext_ports'] = sorted(list(set(ctxt['ext_ports'])))
if config('haproxy-enable-proxy-protocol'):
ctxt['haproxy_enable_proxy_protocol'] = True
return ctxt


Expand Down Expand Up @@ -1787,11 +1793,19 @@ def __init__(self, name=None, script=None, admin_script=None,
self.admin_process_weight = admin_process_weight
self.public_process_weight = public_process_weight

def enable_modules(self):
# NOTE(seyeongkim): this is for proxy protocol and non ssl case.
cmd = ['a2enmod', 'remoteip']
check_call(cmd)

def __call__(self):
total_processes = _calculate_workers()
enable_wsgi_socket_rotation = config('wsgi-socket-rotation')
if enable_wsgi_socket_rotation is None:
enable_wsgi_socket_rotation = True

self.enable_modules()

ctxt = {
"service_name": self.service_name,
"user": self.user,
Expand All @@ -1807,6 +1821,8 @@ def __call__(self):
"threads": 1,
"wsgi_socket_rotation": enable_wsgi_socket_rotation,
}
if config('haproxy-enable-proxy-protocol'):
ctxt['haproxy_enable_proxy_protocol'] = True
return ctxt


Expand Down
8 changes: 8 additions & 0 deletions charmhelpers/contrib/openstack/templates/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,19 @@ backend {{ service }}_{{ frontend }}
{% endif -%}
{% endif -%}
{% for unit, address in frontends[frontend]['backends'].items() -%}
{% if haproxy_enable_proxy_protocol -%}
{% if https -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check send-proxy check-send-proxy check-ssl verify none
{% else -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check send-proxy check-send-proxy
{% endif -%}
{% else -%}
{% if https -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check check-ssl verify none
{% else -%}
server {{ unit }} {{ address }}:{{ ports[1] }} check
{% endif -%}
{% endif -%}
{% endfor %}
{% endfor -%}
{% endfor -%}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Listen {{ ext_port }}
ProxyPassReverse / http://localhost:{{ int }}/
ProxyPreserveHost on
RequestHeader set X-Forwarded-Proto "https"
{% if haproxy_enable_proxy_protocol -%}
RemoteIPProxyProtocol On
RemoteIPProxyProtocolExceptions 127.0.0.1 ::1
{% endif -%}
KeepAliveTimeout 75
MaxKeepAliveRequests 1000
</VirtualHost>
Expand Down
12 changes: 12 additions & 0 deletions charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ WSGISocketRotation Off
</IfVersion>
ErrorLog /var/log/apache2/{{ service_name }}_error.log
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
{% if haproxy_enable_proxy_protocol -%}
RemoteIPProxyProtocol On
RemoteIPProxyProtocolExceptions 127.0.0.1 ::1
{% endif -%}

<Directory /usr/bin>
<IfVersion >= 2.4>
Expand Down Expand Up @@ -61,6 +65,10 @@ WSGISocketRotation Off
</IfVersion>
ErrorLog /var/log/apache2/{{ service_name }}_error.log
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
{% if haproxy_enable_proxy_protocol -%}
RemoteIPProxyProtocol On
RemoteIPProxyProtocolExceptions 127.0.0.1 ::1
{% endif -%}

<Directory /usr/bin>
<IfVersion >= 2.4>
Expand Down Expand Up @@ -89,6 +97,10 @@ WSGISocketRotation Off
</IfVersion>
ErrorLog /var/log/apache2/{{ service_name }}_error.log
CustomLog /var/log/apache2/{{ service_name }}_access.log combined
{% if haproxy_enable_proxy_protocol -%}
RemoteIPProxyProtocol On
RemoteIPProxyProtocolExceptions 127.0.0.1 ::1
{% endif -%}

<Directory /usr/bin>
<IfVersion >= 2.4>
Expand Down
103 changes: 102 additions & 1 deletion tests/contrib/openstack/test_os_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ def relation_units(self, relation_id):
'haproxy-client-timeout': 50000,
}

PROXY_PROTOCOL_CONFIG = {
'haproxy-enable-proxy-protocol': True,
}

CEPH_RELATION = {
'ceph:0': {
'ceph/0': {
Expand Down Expand Up @@ -2972,6 +2976,64 @@ def test_haproxy_context_with_prometheus_exporter(self, local_unit, local_addres
call('cluster'),
call('haproxy-exporter')])

@patch('charmhelpers.contrib.openstack.context.local_address')
@patch('charmhelpers.contrib.openstack.context.local_unit')
def test_haproxy_context_with_data_proxy_protocol(self, local_unit, local_address):
'''Test haproxy context with proxy protocol enabled'''
cluster_relation = {
'cluster:0': {
'peer/1': {
'private-address': 'cluster-peer1.localnet',
},
'peer/2': {
'private-address': 'cluster-peer2.localnet',
},
},
}
local_unit.return_value = 'peer/0'
self.get_relation_ip.side_effect = [None, None, None,
'cluster-peer0.localnet']
relation = FakeRelation(cluster_relation)
self.relation_ids.side_effect = relation.relation_ids
self.relation_get.side_effect = relation.get
self.related_units.side_effect = relation.relation_units
self.get_netmask_for_address.return_value = '255.255.0.0'
self.config.return_value = False
self.maxDiff = None
self.config.side_effect = fake_config(PROXY_PROTOCOL_CONFIG)
self.is_ipv6_disabled.return_value = True
haproxy = context.HAProxyContext()
with patch_open() as (_open, _file):
result = haproxy()
ex = {
'frontends': {
'cluster-peer0.localnet': {
'network': 'cluster-peer0.localnet/255.255.0.0',
'backends': collections.OrderedDict([
('peer-0', 'cluster-peer0.localnet'),
('peer-1', 'cluster-peer1.localnet'),
('peer-2', 'cluster-peer2.localnet'),
]),
}
},
'default_backend': 'cluster-peer0.localnet',
'local_host': '127.0.0.1',
'haproxy_host': '0.0.0.0',
'ipv6_enabled': False,
'stat_password': 'testpassword',
'stat_port': '8888',
'haproxy_enable_proxy_protocol': True,
}
# Verify the context is generated with proxy protocol enabled.
self.assertEqual(ex, result)
# Verify /etc/default/haproxy is updated.
self.assertEqual(_file.write.call_args_list,
[call('ENABLED=1\n')])
self.get_relation_ip.assert_has_calls([call('admin', None),
call('internal', None),
call('public', None),
call('cluster')])

def test_https_context_with_no_https(self):
'''Test apache2 https when no https data available'''
apache = context.ApacheSSLContext()
Expand Down Expand Up @@ -3003,6 +3065,8 @@ def _https_context_setup(self):
('10.5.2.100', '10.5.2.1'),
('10.5.3.100', '10.5.3.1'),
]
config = {'haproxy-enable-proxy-protocol': False}
self.config.side_effect = lambda key: config[key]
apache.external_ports = '8776'
apache.service_namespace = 'cinder'

Expand Down Expand Up @@ -3075,7 +3139,7 @@ def test_https_context_loads_correct_apache_mods(self):
# Test apache2 context also loads required apache modules
apache = context.ApacheSSLContext()
apache.enable_modules()
ex_cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers']
ex_cmd = ['a2enmod', 'ssl', 'proxy', 'proxy_http', 'headers', 'remoteip']
self.check_call.assert_called_with(ex_cmd)

def test_https_configure_cert(self):
Expand Down Expand Up @@ -3699,6 +3763,43 @@ def test_wsgi_worker_config_context_user_and_group(self,
}
self.assertEqual(expect, ctxt())

def test_wsgi_worker_config_context_apache_mods(self):
# Test apache2 context also loads required apache modules
apache = context.WSGIWorkerConfigContext()
apache.enable_modules()
ex_cmd = ['a2enmod', 'remoteip']
self.check_call.assert_called_with(ex_cmd)

@patch.object(context, '_calculate_workers')
def test_wsgi_worker_config_context_proxy_protocol(self,
_calculate_workers):
# Test WSGI worker config context with proxy protocol enabled
self.config.side_effect = fake_config({
'worker-multiplier': 2,
'non-defined-wsgi-socket-rotation': True,
'haproxy-enable-proxy-protocol': True
})
_calculate_workers.return_value = 8
service_name = 'service-name'
script = '/usr/bin/script'
ctxt = context.WSGIWorkerConfigContext(name=service_name,
script=script)
expect = {
"service_name": service_name,
"user": service_name,
"group": service_name,
"script": script,
"admin_script": None,
"public_script": None,
"processes": 8,
"admin_processes": 2,
"public_processes": 6,
"threads": 1,
"wsgi_socket_rotation": True,
"haproxy_enable_proxy_protocol": True
}
self.assertEqual(expect, ctxt())

def test_zeromq_context_unrelated(self):
self.is_relation_made.return_value = False
self.assertEquals(context.ZeroMQContext()(), {})
Expand Down