9662e8b4ab
* Feature (rhel7/httpd 2.4) : hardening apache and code refactoring * remove hard returns * Add default Listen 80 in httpd.conf In case there no vhosts defined in pillar httpd will listen on port 80. Without this default it will not start * empty file autoindex.conf instead of deleting it * explicit hardening items and references from CIS * add #3.5 hardening rule * explain CIS recommendations categories * add dependencies before start service * add recommendation #7.1 Install mod_ssl * link in readme to hardening doc
431 lines
18 KiB
Python
431 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
'''
|
|
apache directives
|
|
:maintainer: "karim Hamza"
|
|
|
|
|
|
'''
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
# Import python libs
|
|
import re
|
|
from copy import deepcopy
|
|
|
|
|
|
from salt.exceptions import CommandExecutionError
|
|
from salt.ext.six.moves import range
|
|
|
|
|
|
def _get_directive_values(directive, d_list):
|
|
'''
|
|
Returns list all values of directive
|
|
'''
|
|
values = [item.get(directive) for item in d_list if directive in item]
|
|
|
|
return values
|
|
|
|
|
|
def get_directive_single_value(directive, d_list, default=None):
|
|
'''
|
|
Returns single value of directive
|
|
|
|
default is returned if directive is absent from list
|
|
'''
|
|
values = _get_directive_values(directive, d_list)
|
|
try:
|
|
return values[0]
|
|
except IndexError:
|
|
if default is not None:
|
|
return default
|
|
|
|
error_msg = "invalid Pillar content - " \
|
|
+ directive + " - is not defined"
|
|
raise CommandExecutionError(error_msg)
|
|
|
|
|
|
def append_to_container_directives(directive, value, container):
|
|
'''
|
|
Append directive to directives list
|
|
'''
|
|
try:
|
|
container['directives'].append({directive: value})
|
|
except KeyError:
|
|
container['directives'] = []
|
|
container['directives'].append({directive: value})
|
|
|
|
return container
|
|
|
|
|
|
def _manage_directive_into_containers(directive,
|
|
value,
|
|
container,
|
|
container_name_target,
|
|
item,
|
|
enforce_value=False,
|
|
add_directive=True):
|
|
'''
|
|
Enforce value for directive into specific container
|
|
|
|
directive
|
|
directive label (name)
|
|
|
|
value
|
|
value to enforce
|
|
|
|
container
|
|
container to parse
|
|
|
|
container_name_target
|
|
container name target into directive/value have to be enforced
|
|
|
|
item
|
|
name of the item target
|
|
|
|
enforce_value : default=False
|
|
True: enforce value if directive exists, otherwise add it if add_directive=True
|
|
|
|
add_directive : default=True
|
|
Only if enforce_value=False add directive if it is not present
|
|
'''
|
|
for n_container, l_containers in container.get('containers', {}).items():
|
|
for idx, nested_container in enumerate(l_containers):
|
|
if (n_container == container_name_target
|
|
and nested_container['item'] == item):
|
|
if enforce_value:
|
|
container['containers'][n_container][idx] = \
|
|
enforce_directive_value(directive,
|
|
{'value': value, 'add_if_absent': add_directive},
|
|
n_container,
|
|
nested_container)
|
|
else:
|
|
container['containers'][n_container][idx] = \
|
|
append_to_container_directives(directive,
|
|
value,
|
|
nested_container)
|
|
|
|
container['containers'][n_container][idx] = \
|
|
_manage_directive_into_containers(directive,
|
|
value,
|
|
nested_container,
|
|
container_name_target,
|
|
item,
|
|
enforce_value,
|
|
add_directive)
|
|
|
|
return container
|
|
|
|
|
|
def set_vhost_logging_directives(container, servername, logdir):
|
|
'''
|
|
set value of CustomLog and LogFormat directives in vhost
|
|
'''
|
|
logformat = get_directive_single_value('LogFormat',
|
|
container.get('directives', []),
|
|
default='combined')
|
|
|
|
enforce_directive_value(
|
|
directive='CustomLog',
|
|
enforced_directive_data=
|
|
{'value': logdir + '/' + servername +'-access.log ' + logformat,
|
|
'add_if_absent': True},
|
|
container_name='VirtualHost',
|
|
container_data=container)
|
|
enforce_directive_value(
|
|
directive='ErrorLog',
|
|
enforced_directive_data=
|
|
{'value': logdir + '/' + servername +'-error.log ',
|
|
'add_if_absent': True},
|
|
container_name='VirtualHost',
|
|
container_data=container)
|
|
|
|
return container
|
|
|
|
|
|
def _container_merge_multiple_directives(container):
|
|
'''
|
|
append directives_multiple list into directives
|
|
'''
|
|
try:
|
|
container['directives'].extend(container.get('directives_multiple', []))
|
|
except KeyError:
|
|
container['directives'] = []
|
|
container['directives'] = container.get('directives_multiple', [])
|
|
|
|
container.pop('directives_multiple', None)
|
|
|
|
for sub_container_name, sub_containers_list in container.get('containers', {}).items():
|
|
for sub_idx, sub_container in enumerate(sub_containers_list):
|
|
container['containers'][sub_container_name][sub_idx] = \
|
|
_container_merge_multiple_directives(sub_container)
|
|
|
|
return container
|
|
|
|
|
|
def merge_container_with_additional_data(container_to_update,
|
|
container_to_import,
|
|
add_directive=True,
|
|
add_container=True):
|
|
'''
|
|
Merge containers usually to merge default values with pillar content
|
|
|
|
container_to_update
|
|
the default container into which put or modify values with pillar content
|
|
|
|
container_to_import
|
|
usually pillar content
|
|
|
|
add_directive : default=True
|
|
add directive if it is not present
|
|
|
|
add_container : default=True
|
|
add sub_container if it is absent in container_to_update
|
|
'''
|
|
merged_container = deepcopy(container_to_update)
|
|
multiple_directives_to_append = []
|
|
for mult_directive_item in container_to_update.get('directives_multiple', []):
|
|
for mult_directive, imp_value in mult_directive_item.items():
|
|
append_to_container_directives(mult_directive,
|
|
imp_value,
|
|
merged_container)
|
|
if mult_directive not in multiple_directives_to_append:
|
|
multiple_directives_to_append.append(mult_directive)
|
|
merged_container.pop('directives_multiple', None)
|
|
|
|
for p_directive_item in container_to_import.get('directives', []):
|
|
for p_directive, p_value in p_directive_item.items():
|
|
if p_directive in multiple_directives_to_append:
|
|
append_to_container_directives(p_directive,
|
|
p_value,
|
|
merged_container)
|
|
else:
|
|
merged_container = enforce_directive_value(
|
|
p_directive,
|
|
{'value': p_value, 'add_if_absent': add_directive},
|
|
'virtual_name_container',
|
|
merged_container)
|
|
# containers:
|
|
sub_containers_to_update = merged_container.get('containers', {})
|
|
sub_containers_to_import = container_to_import.get('containers', {})
|
|
if sub_containers_to_update and sub_containers_to_import:
|
|
# merge directives of sub containers
|
|
for container_name, u_container_list in sub_containers_to_update.items():
|
|
to_imp_containers = sub_containers_to_import.get(container_name, [])
|
|
for container_idx, to_upd_container_data in enumerate(u_container_list):
|
|
imp_items_containers = [container for container in to_imp_containers
|
|
if container['item'] == to_upd_container_data['item']]
|
|
for i_item_container in imp_items_containers:
|
|
merged_container['containers'][container_name][container_idx] = \
|
|
merge_container_with_additional_data(
|
|
merged_container['containers'][container_name][container_idx],
|
|
i_item_container,
|
|
add_directive)
|
|
if add_container:
|
|
# merge containers not present in default 'container_name' list
|
|
d_container_items = set([container.get('item') for container
|
|
in u_container_list])
|
|
p_container_items = set([container.get('item') for container
|
|
in to_imp_containers])
|
|
items_diff = (p_container_items - d_container_items)
|
|
for item in items_diff:
|
|
merged_container['containers'][container_name].extend(
|
|
[container for container in to_imp_containers if
|
|
container.get('item') == item])
|
|
|
|
if add_container:
|
|
# merge global containers not present in default
|
|
k_containers_diff = (set(sub_containers_to_import.keys())
|
|
- set(sub_containers_to_update.keys()))
|
|
for k_container in k_containers_diff:
|
|
merged_container['containers'][k_container] = {}
|
|
merged_container['containers'][k_container] = sub_containers_to_import[k_container]
|
|
|
|
elif not sub_containers_to_update \
|
|
and sub_containers_to_import \
|
|
and add_container:
|
|
merged_container['containers'] = {}
|
|
merged_container['containers'] = sub_containers_to_import
|
|
elif not sub_containers_to_import:
|
|
pass
|
|
|
|
# move directives_multiple into directives and delete directives_multiple
|
|
for container_name, containers_list in merged_container.get('containers', {}).items():
|
|
for container_idx, container_data in enumerate(containers_list):
|
|
merged_container['containers'][container_name][container_idx] = \
|
|
_container_merge_multiple_directives(container_data)
|
|
|
|
return merged_container
|
|
|
|
|
|
def enforce_security_directives_into_containers(container_to_secure,
|
|
secured_containers,
|
|
add_directive=True,
|
|
add_container=True):
|
|
'''
|
|
Merge secured containers into pillar content
|
|
|
|
container_to_secure
|
|
usually pillar content
|
|
|
|
secured_containers
|
|
content of hadened values
|
|
|
|
add_directive : default=True
|
|
add directive if it is not present
|
|
|
|
add_container : default=True
|
|
add sub_container if it is absent in container_to_secure
|
|
'''
|
|
i_secured_containers = {}
|
|
i_secured_containers['containers'] = secured_containers
|
|
container_to_secure = merge_container_with_additional_data(
|
|
container_to_secure,
|
|
i_secured_containers,
|
|
add_directive=add_directive,
|
|
add_container=add_container)
|
|
# search in (sub) nested containers and secure them
|
|
for secure_container_name, l_s_containers in secured_containers.items():
|
|
for s_container in l_s_containers:
|
|
# search into container_to_secure
|
|
secured_item = s_container.get('item')
|
|
for s_directive in s_container.get('directives', []):
|
|
for s_d_label, s_d_value in s_directive.items():
|
|
container_to_secure = _manage_directive_into_containers(
|
|
s_d_label,
|
|
s_d_value,
|
|
container_to_secure,
|
|
container_name_target=secure_container_name,
|
|
item=secured_item,
|
|
enforce_value=True,
|
|
add_directive=add_directive)
|
|
|
|
return container_to_secure
|
|
|
|
|
|
def _substitute_value(text, enforced_value):
|
|
'''
|
|
conditional replace in 'text' with regex and condition
|
|
|
|
text
|
|
string to process
|
|
|
|
enforced_value
|
|
dict :
|
|
match: regex to match
|
|
value: value to enforce
|
|
onlyif_pillar_is: condition on pillar content
|
|
regex_group_position: number of group to replace in regex
|
|
'''
|
|
def my_match_function(m_object):
|
|
|
|
return_value = ''.join([m_object.group(idx) for idx in range(1, position)
|
|
if m_object.group(idx) is not None])
|
|
if condition == 'greater':
|
|
return_value = return_value \
|
|
+ str(min(int(m_object.group(position)), int(enforced_value['value']))) \
|
|
+ ''.join([m_object.group(idx) for idx in range(position+1, m_object.lastindex+1) if m_object.group(idx) is not None])
|
|
elif condition == 'lower':
|
|
return_value = return_value \
|
|
+ str(max(int(m_object.group(position)), int(enforced_value['value']))) \
|
|
+ ''.join([m_object.group(idx) for idx in range(position+1, m_object.lastindex+1) if m_object.group(idx) is not None])
|
|
|
|
elif condition == 'different' and m_object.group(position) != str(enforced_value['value']):
|
|
return_value = return_value \
|
|
+ enforced_value['value'] \
|
|
+ ''.join([m_object.group(idx) for idx in range(position+1, m_object.lastindex+1) if m_object.group(idx) is not None])
|
|
else:
|
|
return_value = m_object.group(0)
|
|
|
|
return return_value
|
|
|
|
_pattern = re.compile(enforced_value.get('match', r'(\S+(\s+\S+)*)'), re.IGNORECASE)
|
|
condition = enforced_value.get('onlyif_pillar_is', 'different')
|
|
position = enforced_value.get('regex_group_position', 1)
|
|
value = _pattern.sub(my_match_function, str(text))
|
|
|
|
return value
|
|
|
|
|
|
def enforce_directive_value(directive,
|
|
enforced_directive_data,
|
|
container_name,
|
|
container_data):
|
|
'''
|
|
Enforce value of directive under conditions
|
|
|
|
directive
|
|
directive label (name)
|
|
|
|
enforced_directive_data
|
|
dict containning
|
|
value to put
|
|
condition (greater|lower|different)
|
|
regex match : default= r'(\\w+(\\s+\\w+)*)'
|
|
regex group position : default=1
|
|
container : enforce value only on the specified container
|
|
|
|
container_name
|
|
the name of httpd container
|
|
|
|
container_data
|
|
container to parse
|
|
'''
|
|
d_is_present = False
|
|
add_directive = enforced_directive_data.get('add_if_absent', False)
|
|
enforced_data_values = enforced_directive_data.get('values', [enforced_directive_data])
|
|
for idx_d, d_item in enumerate(container_data.get('directives', [])):
|
|
if directive in d_item:
|
|
d_is_present = True
|
|
for enforced_data_value in enforced_data_values:
|
|
if (not enforced_data_value.get('container', '')) \
|
|
or (enforced_data_value.get('container') == container_name):
|
|
container_data['directives'][idx_d][directive] = \
|
|
_substitute_value(container_data['directives'][idx_d][directive],
|
|
enforced_data_value)
|
|
if re.match(r'(\s*)?$', container_data['directives'][idx_d][directive]) is not None:
|
|
# delete directive from list in case of
|
|
# the value is empty after replacement
|
|
del container_data['directives'][idx_d]
|
|
break
|
|
if add_directive and not d_is_present \
|
|
and not enforced_directive_data.get('match', '') \
|
|
and not enforced_directive_data.get('values', ''):
|
|
append_to_container_directives(directive,
|
|
enforced_directive_data.get('value'),
|
|
container_data)
|
|
|
|
# directive is not added in subcontainers
|
|
enforced_directive_data['add_if_absent'] = False
|
|
|
|
for sub_container_name, sub_containers in \
|
|
container_data.get('containers', {}).items():
|
|
container_to_match = enforced_directive_data.get('container', sub_container_name)
|
|
if container_to_match == sub_container_name:
|
|
for idx, nested_container in enumerate(sub_containers):
|
|
container_data['containers'][sub_container_name][idx] = \
|
|
enforce_directive_value(directive,
|
|
enforced_directive_data,
|
|
sub_container_name,
|
|
nested_container)
|
|
|
|
return container_data
|
|
|
|
|
|
def remove_container(container_data,
|
|
container_name_to_remove,
|
|
item_name_to_remove):
|
|
'''
|
|
remove container_name/item from container_data
|
|
'''
|
|
for idx, container in enumerate(container_data.get('containers', {}).get(container_name_to_remove, [])):
|
|
if container.get('item') == item_name_to_remove:
|
|
del container_data['containers'][container_name_to_remove][idx]
|
|
|
|
for sub_container_name, sub_containers in \
|
|
container_data.get('containers', {}).items():
|
|
for sub_idx, sub_container in enumerate(sub_containers):
|
|
container_data['containers'][sub_container_name][sub_idx] = \
|
|
remove_container(sub_container, container_name_to_remove, item_name_to_remove)
|
|
|
|
return container_data
|