adds firewalld configuration based on enabled services

This change introduces automated configuration of firewalld and adds
a new filter for extracting services from the project_services dict.
the filter selects any enabled services and their haproxy element
and returns them so they can be iterated over.
This commit also enables automated configuration of firewalld from enabled
openstack services and adds them to the defined zone and reloads the
system firewall.

Change-Id: Iea3680142711873984efff2b701347b6a56dd355
This commit is contained in:
k-s-dean 2022-06-28 12:18:35 +01:00
parent fde5eeec29
commit 8553e52acd
10 changed files with 589 additions and 8 deletions

View File

@ -528,6 +528,12 @@ internal_protocol: "{{ 'https' if kolla_enable_tls_internal | bool else 'http' }
# TODO(yoctozepto): Remove after Zed. Kept for compatibility only.
admin_protocol: "{{ internal_protocol }}"
##################
# Firewall options
##################
enable_external_api_firewalld: "false"
external_api_firewalld_zone: "public"
####################
# OpenStack options
####################

View File

@ -0,0 +1,6 @@
---
- name: Reload firewalld
become: True
service:
name: "firewalld"
state: reloaded

View File

@ -21,3 +21,21 @@
with_dict: "{{ project_services }}"
notify:
- Restart haproxy container
- name: "Configuring firewall for {{ project_name }}"
firewalld:
offline: "yes"
permanent: "yes"
port: "{{ item.value.port }}/tcp"
state: "enabled"
zone: "{{ external_api_firewalld_zone }}"
become: true
when:
- enable_haproxy | bool
- item.value.enabled | bool
- item.value.port is defined
- item.value.external | default('false') | bool
- enable_external_api_firewalld | bool
with_dict: "{{ project_services | extract_haproxy_services }}"
notify:
- "Reload firewalld"

View File

@ -838,3 +838,23 @@
- inventory_hostname in groups['loadbalancer']
- haproxy_stat.find('vitrage_api') == -1
- haproxy_vip_prechecks
- name: Firewalld checks
block:
- name: Check if firewalld is running # noqa command-instead-of-module
become: true
command:
cmd: "systemctl is-active firewalld"
register: firewalld_is_active
changed_when: false
failed_when: false
- name: Fail if firewalld is not running
fail:
msg: >-
firewalld is not running.
Please install and configure firewalld.
when:
- firewalld_is_active.rc != 0
when:
- enable_external_api_firewalld | bool

View File

@ -196,18 +196,21 @@ file. Example:
}
Disabling firewalls
~~~~~~~~~~~~~~~~~~~
Enabling/Disabling firewalls
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Kolla Ansible does not support configuration of host firewalls, and instead
attempts to disable them.
Kolla Ansible supports configuration of host firewalls.
On Debian family systems where the UFW firewall is enabled, a default policy
will be added to allow all traffic.
Currently only Firewalld is supported.
On Red Hat family systems where firewalld is installed, it will be disabled.
On Debian family systems Firewalld will need to be installed beforehand.
This behaviour can be avoided by setting ``disable_firewall`` to ``false``.
On Red Hat family systems firewalld should be installed by default.
To enable configuration of the system firewall set ``disable_firewall``
to ``false`` and set ``enable_external_api_firewalld`` to ``true``.
For further information. See :doc:`../../user/security`
Creation of Python virtual environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -76,3 +76,84 @@ via SSH) is default configuration owner and group in target nodes.
From Rocky release, Kolla support connection using any user which has
passwordless sudo capability. For setting custom owner user and group, user
can set ``config_owner_user`` and ``config_owner_group`` in ``globals.yml``.
FirewallD
~~~~~~~~~
Prior to Zed, Kolla Ansible would disable any system firewall leaving
configuration up to the end users. Firewalld is now supported and will
configure external api ports for each enabled OpenStack service.
The following variables should be configured in Kolla Ansible's
``globals.yml``
* external_api_firewalld_zone
* The default zone to configure ports on for external API Access
* String - defaults to the public zone
* enable_external_api_firewalld
* Setting to true will enable external API ports configuration
* Bool - set to true or false
* disable_firewall
* Setting to false will stop Kolla Ansible
from disabling the systems firewall
* Bool - set to true or false
Prerequsites
============
Firewalld needs to be installed beforehand.
Kayobe can be used to automate the installation and configuration of firewalld
before running Kolla Ansible. If you do not use Kayobe you must ensure that
that firewalld has been installed and setup correctly.
You can check the current active zones by running the command below.
If the output of the command is blank then no zones are configured as active.
.. code-block:: console
sudo firewall-cmd --get-active-zones
You should ensure that the system is reachable via SSH to avoid lockout,
to add ssh to a particular zone run the following command.
.. code-block:: console
sudo firewall-cmd --permanent --zone=<zone> --add-service=ssh
You should also set the required interface on a particular zone by running the
below command. This will mark the zone as active on the specified interface.
.. code-block:: console
sudo firewall-cmd --permanent --zone=<zone> --change-interface=<interface>
if more than one interface is required on a specific zone this can be achieved
by running
.. code-block:: console
sudo firewall-cmd --permanent --zone=public --add-interface=<additional interface>
Any other ports that need to be opened on the system should be done
before hand. The following command will add additional ports to a zone
.. code-block:: console
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
Dependent on your infrastructure security policy you may wish to add a policy
of drop on the public zone this can be achieved by running the following
command.
.. code-block:: console
sudo firewall-cmd --permanent --set-target=DROP --zone=public
To apply changes to the system firewall run
.. code-block:: console
sudo firewalld-cmd --reload
For additional information and configuration please see:
https://firewalld.org/documentation/man-pages/firewall-cmd.html

View File

@ -206,6 +206,15 @@ workaround_ansible_issue_8743: yes
#default_container_healthcheck_retries: 3
#default_container_healthcheck_start_period: 5
##################
# Firewall options
##################
# Configures firewalld on both ubuntu and centos systems
# for enabled services.
# firewalld should be installed beforehand.
# disable_firewall: "true"
# enable_external_api_firewalld: "false"
# external_api_firewalld_zone: "public"
#############
# TLS options

View File

@ -34,6 +34,28 @@ def service_enabled(context, service):
return _call_bool_filter(context, enabled)
@jinja2.pass_context
def extract_haproxy_services(context, services):
"""Return a Dict of haproxy services
:param context: Jinja2 Context object.
:param service: Services definition, dict.
:returns: A Dict.
"""
haproxy = {}
for key in services:
service = services.get(key)
if service_enabled(context, service):
service_haproxy = service.get('haproxy')
if service_haproxy:
if not set(haproxy).isdisjoint(set(service_haproxy)):
raise exception.FilterError(
"haproxy service names should be unique")
haproxy.update(service_haproxy)
return haproxy
@jinja2.pass_context
def service_mapped_to_host(context, service):
"""Return whether a service is mapped to this host.
@ -89,6 +111,7 @@ def select_services_enabled_and_mapped_to_host(context, services):
def get_filters():
return {
"extract_haproxy_services": extract_haproxy_services,
"service_enabled": service_enabled,
"service_mapped_to_host": service_mapped_to_host,
"service_enabled_and_mapped_to_host": (

View File

@ -110,6 +110,411 @@ class TestFilters(unittest.TestCase):
filters.service_mapped_to_host, self.context,
service)
def test_extract_haproxy_services_empty_dict(self):
example_service = {}
actual = filters.extract_haproxy_services(
self.context, example_service)
# No change
self.assertDictEqual({}, actual)
def test_extract_haproxy_services_no_haproxy_dict(self):
example_service = {
"keystone-ssh": {
"container_name": "keystone_ssh",
"dimensions": {},
"enabled": True,
"group": "keystone",
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_listen sshd 8023"
],
"timeout": "30"
},
"image": "keystone-ssh:latest",
"volumes": [
"/etc/kolla/keystone-ssh/:/var/lib/kolla/config_files/:ro",
"/etc/localtime:/etc/localtime:ro",
"",
"kolla_logs:/var/log/kolla/",
"keystone_fernet_tokens:/etc/keystone/fernet-keys"
]
}
}
actual = filters.extract_haproxy_services(self.context,
example_service)
self.assertDictEqual({}, actual)
def test_extract_haproxy_services_haproxy_dict(self):
example_service = {
"keystone": {
"container_name": "keystone",
"dimensions": {},
"enabled": True,
"group": "keystone",
"haproxy": {
"keystone_admin": {
"enabled": True,
"external": False,
"listen_port": "35357",
"mode": "http",
"port": "35357",
"tls_backend": True
},
"keystone_external": {
"backend_http_extra": [],
"enabled": True,
"external": True,
"listen_port": "5000",
"mode": "http",
"port": "5000",
"tls_backend": True
},
"keystone_internal": {
"backend_http_extra": [],
"enabled": True,
"external": False,
"listen_port": "5000",
"mode": "http",
"port": "5000",
"tls_backend": True
}
},
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_curl https://1.2.3.4:5000"
],
"timeout": "30"
},
"image": "keystone:latest",
"volumes": [
"/etc/kolla/keystone/:/var/lib/kolla/config_files/:ro",
"/etc/localtime:/etc/localtime:ro",
"",
"",
"kolla_logs:/var/log/kolla/",
"keystone_fernet_tokens:/etc/keystone/fernet-keys"
]
}
}
expected = {
'keystone_admin': {
'enabled': True,
'external': False,
'listen_port': '35357',
'mode': 'http',
'port': '35357',
'tls_backend': True
},
'keystone_external': {
'backend_http_extra': [],
'enabled': True,
'external': True,
'listen_port': '5000',
'mode': 'http',
'port': '5000',
'tls_backend': True
},
'keystone_internal': {
'backend_http_extra': [],
'enabled': True,
'external': False,
'listen_port': '5000',
'mode': 'http',
'port': '5000',
'tls_backend': True
}
}
actual = filters.extract_haproxy_services(self.context,
example_service)
self.assertDictEqual(expected, actual)
def test_extract_two_services_with_haproxy_dict(self):
example_service = {
"glance-api": {
"container_name": "glance_api",
"dimensions": {},
"enabled": True,
"environment": {
"http_proxy": "",
"https_proxy": "",
"no_proxy": "127.0.0.1,localhost,1.2.3.4,1.2.3.4"
},
"group": "glance-api",
"haproxy": {
"glance_api": {
"backend_http_extra": [
"timeout server 6h"
],
"custom_member_list": [
"server someserver 1.2.3.4:9292 "
"check inter 2000 rise 2 fall 5",
""
],
"enabled": False,
"external": False,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292"
},
"glance_api_external": {
"backend_http_extra": [
"timeout server 6h"
],
"custom_member_list": [
"server someserver 1.2.3.4:9292 "
"check inter 2000 rise 2 fall 5",
""
],
"enabled": False,
"external": True,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292"
}
},
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_curl http://localhost:9292"
],
"timeout": "30"
},
"host_in_groups": True,
"image": "centos-source-glance-api:latest",
"privileged": False,
"volumes": [
"/etc/localtime:/etc/localtime:ro",
"",
"glance:/var/lib/glance/",
"",
"kolla_logs:/var/log/kolla/",
"",
""
]
},
"glance-tls-proxy": {
"container_name": "glance_tls_proxy",
"dimensions": {},
"enabled": True,
"group": "glance-api",
"haproxy": {
"glance_tls_proxy": {
"backend_http_extra": [
"timeout server 6h"
],
"custom_member_list": [
"server someserver 1.2.3.4:9292 "
"check inter 2000 rise 2 fall 5 ssl verify "
"required ca-file ca-bundle.trust.crt",
""
],
"enabled": True,
"external": False,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292",
"tls_backend": "yes"
},
"glance_tls_proxy_external": {
"backend_http_extra": [
"timeout server 6h"
],
"custom_member_list": [
"server someserver 1.2.3.4:9292 "
"check inter 2000 rise 2 fall 5 ssl verify "
"required ca-file ca-bundle.trust.crt",
""
],
"enabled": True,
"external": True,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292",
"tls_backend": "yes"
}
},
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_curl -u openstack:asdf 1.2.3.4:9293"
],
"timeout": "30"
},
"host_in_groups": True,
"image": "centos-source-haproxy:latest",
"volumes": [
"/etc/localtime:/etc/localtime:ro",
"",
"kolla_logs:/var/log/kolla/"
]
}
}
expected = {
"glance_api": {
"backend_http_extra": [
"timeout server 6h"
],
'custom_member_list': ['server someserver '
'1.2.3.4:9292 check inter 2000 '
'rise 2 fall 5',
''],
"enabled": False,
"external": False,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292"
},
"glance_api_external": {
"backend_http_extra": [
"timeout server 6h"
],
'custom_member_list': ['server someserver '
'1.2.3.4:9292 check inter 2000 '
'rise 2 fall 5',
''],
"enabled": False,
"external": True,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292"
},
"glance_tls_proxy": {
"backend_http_extra": [
"timeout server 6h"
],
'custom_member_list': ['server someserver 1.2.3.4:9292 '
'check inter 2000 rise 2 fall 5 '
'ssl verify required ca-file '
'ca-bundle.trust.crt',
''],
"enabled": True,
"external": False,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292",
"tls_backend": "yes"
},
"glance_tls_proxy_external": {
"backend_http_extra": [
"timeout server 6h"
],
'custom_member_list': ['server someserver 1.2.3.4:9292 '
'check inter 2000 rise 2 fall 5 '
'ssl verify required ca-file '
'ca-bundle.trust.crt',
''],
"enabled": True,
"external": True,
"frontend_http_extra": [
"timeout client 6h"
],
"mode": "http",
"port": "9292",
"tls_backend": "yes"
}
}
actual = filters.extract_haproxy_services(self.context,
example_service)
self.assertDictEqual(expected, actual)
def test_extract_haproxy_services_haproxy_dict_duplicate(self):
example_service = {
"keystone": {
"container_name": "keystone",
"dimensions": {},
"enabled": True,
"group": "keystone",
"haproxy": {
"keystone_admin": {
"enabled": True,
"external": False,
"listen_port": "35357",
"mode": "http",
"port": "35357",
"tls_backend": True
},
},
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_curl https://1.2.3.4:5000"
],
"timeout": "30"
},
"image": "keystone:latest",
"volumes": [
"/etc/kolla/keystone/:/var/lib/kolla/config_files/:ro",
"kolla_logs:/var/log/kolla/"
]
},
"keystone-ssh": {
"container_name": "keystone_ssh",
"dimensions": {},
"enabled": True,
"group": "keystone",
"haproxy": {
"keystone_admin": {
"enabled": True,
"external": False,
"listen_port": "35357",
"mode": "http",
"port": "35357",
"tls_backend": True
},
},
"healthcheck": {
"interval": "30",
"retries": "3",
"start_period": "5",
"test": [
"CMD-SHELL",
"healthcheck_listen sshd 8023"
],
"timeout": "30"
},
"image": "keystone-ssh:latest",
"volumes": [
"/etc/kolla/keystone-ssh/:/var/lib/kolla/config_files/:ro",
"kolla_logs:/var/log/kolla/"
]
}
}
self.assertRaises(exception.FilterError,
filters.extract_haproxy_services,
self.context, example_service)
@mock.patch.object(filters, 'service_enabled')
@mock.patch.object(filters, 'service_mapped_to_host')
def test_service_enabled_and_mapped_to_host(self, mock_mapped,

View File

@ -0,0 +1,10 @@
---
features:
- |
Enables configuring firewalld for external API services.
Extracts the required services and checks the external port,
then adds the ports to a firewalld zone.
Assumes that firewalld has been installed and configured beforehand.
The variable disable_firewall, is disabled by default to preserve
backwards compatibility.
But its good practice to have the system firewall configured.