* 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 docpull/261/head
@@ -0,0 +1,105 @@ | |||
# Hardening list | |||
This formula enforce security recommandations from [CIS Benchmarks](https://www.cisecurity.org/cis-benchmarks/) website | |||
From ***CIS_Apache_HTTP_Server_2.4_Benchmark_v1.4.pdf*** document | |||
> A scoring status indicates whether compliance with the given recommendation impacts the assessed target's benchmark score. | |||
> Items in [*level 2*] profile exhibit one or more of the following characteristics: | |||
> - are intended for environments or use cases where security is paramount | |||
> - acts as defense in depth measure | |||
> - may negatively inhibit the utility or performance of the technology | |||
In this formula we focus on (**Scored**) [*level* ***1***] items | |||
## List of all items with their CIS references | |||
## 2. Minimize Apache Modules | |||
- [ ] 2.1 Enable Only Necessary Authentication and Authorization Modules (Not Scored) | |||
- [X] 2.2 Enable the Log Config Module (**Scored**) | |||
- [X] 2.3 Disable WebDAV Modules (**Scored**) | |||
- [X] 2.4 Disable Status Module (**Scored**) | |||
- [X] 2.5 Disable Autoindex Module (**Scored**) | |||
- [ ] 2.6 Disable Proxy Modules (**Scored**) | |||
- [X] 2.7 Disable User Directories Modules (**Scored**) | |||
- [X] 2.8 Disable Info Module (**Scored**) | |||
## 3. Principles, Permissions, and Ownership | |||
- [X] 3.1 Run the Apache Web Server as a non-root user (**Scored**) | |||
- [X] 3.2 Give the Apache User Account an Invalid Shell (**Scored**) | |||
- [ ] 3.3 Lock the Apache User Account (**Scored**) | |||
- [X] 3.4 Set Ownership on Apache Directories and Files (**Scored**) | |||
- [X] 3.5 Set Group Id on Apache Directories and Files (**Scored**) | |||
- [ ] 3.6 Restrict Other Write Access on Apache Directories and Files (**Scored**) | |||
- [X] 3.7 Secure Core Dump Directory (**Scored**) | |||
- [ ] 3.8 Secure the Lock File (**Scored**) | |||
- [X] 3.9 Secure the Pid File (**Scored**) | |||
- [X] 3.10 Secure the ScoreBoard File (**Scored**) | |||
- [X] 3.11 Restrict Group Write Access for the Apache Directories and Files (**Scored**) | |||
- [X] 3.12 Restrict Group Write Access for the Document Root Directories and Files (**Scored**) | |||
## 4. Apache Access Control | |||
- [X] 4.1 Deny Access to OS Root Directory (**Scored**) | |||
- [ ] 4.2 Allow Appropriate Access to Web Content (Not Scored) | |||
- [X] 4.3 Restrict Override for the OS Root Directory (**Scored**) | |||
- [X] 4.4 Restrict Override for All Directories (**Scored**) | |||
## 5. Minimize Features, Content and Options | |||
- [X] 5.1 Restrict Options for the OS Root Directory (**Scored**) | |||
- [X] 5.2 Restrict Options for the Web Root Directory (**Scored**) | |||
- [X] 5.3 Minimize Options for Other Directories (**Scored**) | |||
- [X] 5.4 Remove Default HTML Content (**Scored**) | |||
- [X] 5.5 Remove Default CGI Content printenv (**Scored**) | |||
- [X] 5.6 Remove Default CGI Content test-cgi (**Scored**) | |||
- [X] 5.7 Limit HTTP Request Methods (**Scored**) | |||
- [X] 5.8 Disable HTTP TRACE Method (**Scored**) | |||
- [X] 5.9 Restrict HTTP Protocol Versions (**Scored**) | |||
- [X] 5.10 Restrict Access to .ht* files (**Scored**) | |||
- [ ] 5.11 Restrict File Extensions [*level 2*] (**Scored**) | |||
- [ ] 5.12 Deny IP Address Based Requests [*level 2*] (**Scored**) | |||
- [ ] 5.13 Restrict Listen Directive [*level 2*] (**Scored**) | |||
- [ ] 5.14 Restrict Browser Frame Options [*level 2*] (**Scored**) | |||
## 6. Operations - Logging, Monitoring and Maintenance | |||
- [X] 6.1 Configure the Error Log (**Scored**) | |||
- [ ] 6.2 Configure a Syslog Facility for Error Logging [*level 2*] (**Scored**) | |||
- [X] 6.3 Configure the Access Log (**Scored**) | |||
- [X] 6.4 Log Storage and Rotation (**Scored**) | |||
- [ ] 6.5 Apply Applicable Patches (**Scored**) | |||
- [ ] 6.6 Install and Enable ModSecurity [*level 2*] (**Scored**) | |||
- [ ] 6.7 Install and Enable OWASP ModSecurity Core Rule Set [*level 2*] (**Scored**) | |||
## 7. SSL/TLS Configuration | |||
- [X] 7.1 Install mod_ssl and/or mod_nss (**Scored**) | |||
- [ ] 7.2 Install a Valid Trusted Certificate (**Scored**) | |||
- [ ] 7.3 Protect the Server's Private Key (**Scored**) | |||
- [X] 7.4 Disable the SSL v3.0 Protocol (**Scored**) | |||
- [ ] 7.5 Restrict Weak SSL/TLS Ciphers (**Scored**) | |||
- [X] 7.6 Disable SSL Insecure Renegotiation (**Scored**) | |||
- [X] 7.7 Ensure SSL Compression is not Enabled (**Scored**) | |||
- [ ] 7.8 Restrict Medium Strength SSL/TLS Ciphers (**Scored**) | |||
- [ ] 7.9 Disable the TLS v1.0 Protocol [*level 2*] (**Scored**) | |||
- [ ] 7.10 Enable OCSP Stapling [*level 2*] (**Scored**) | |||
- [ ] 7.11 Enable HTTP Strict Transport Security [*level 2*] (**Scored**) | |||
## 8. Information Leakage | |||
- [X] 8.1 Set ServerToken to 'Prod' (**Scored**) | |||
- [X] 8.2 Set ServerSignature to 'Off' (**Scored**) | |||
- [ ] 8.3 Information Leakage via Default Apache Content [*level 2*] (**Scored**) | |||
- [ ] 8.4 Information Leakage via ETag [*level 2*] (**Scored**) | |||
## 9. Denial of Service Mitigations | |||
- [X] 9.1 Set TimeOut to 10 or less (**Scored**) | |||
- [X] 9.2 Set the KeepAlive directive to On (**Scored**) | |||
- [X] 9.3 Set MaxKeepAliveRequests to 100 or greater (**Scored**) | |||
- [X] 9.4 Set KeepAliveTimeout Low to Mitigate Denial of Service (**Scored**) | |||
- [X] 9.5 Set Timeout Limits for Request Headers (**Scored**) | |||
- [X] 9.6 Set Timeout Limits for the Request Body (**Scored**) | |||
## 10. Request Limits | |||
- [ ] 10.1 Set the LimitRequestLine directive to 512 or less [*level 2*] (**Scored**) | |||
- [ ] 10.2 Set the LimitRequestFields directive to 100 or less [*level 2*] (**Scored**) | |||
- [ ] 10.3 Set the LimitRequestFieldsize directive to 1024 or less [*level 2*] (**Scored**) | |||
- [ ] 10.4 Set the LimitRequestBody directive to 102400 or less [*level 2*] (**Scored**) | |||
## 11. Enable SELinux to Restrict Apache Processes | |||
- [ ] 11.1 Enable SELinux in Enforcing Mode [*level 2*] (**Scored**) | |||
- [ ] 11.2 Run Apache Processes in the httpd_t Confined Context [*level 2*] (**Scored**) | |||
- [ ] 11.3 Ensure the httpd_t Type is Not in Permissive Mode [*level 2*] (**Scored**) | |||
- [ ] 11.4 Ensure Only the Necessary SELinux Booleans are Enabled [*level 2*] (Not Scored) | |||
## 12. Enable AppArmor to Restrict Apache Processes | |||
- [ ] 12.1 Enable the AppArmor Framework [*level 2*] (**Scored**) | |||
- [ ] 12.2 Customize the Apache AppArmor Profile [*level 2*] (Not Scored) | |||
- [ ] 12.3 Ensure Apache AppArmor Profile is in Enforce Mode [*level 2*] (**Scored**) |
@@ -0,0 +1,149 @@ | |||
====== | |||
apache | |||
====== | |||
Formulas to set up and configure the Apache HTTP server. | |||
This Formula uses the concepts of ``directive`` and ``container`` in pillars | |||
* ``directive`` is an httpd directive https://httpd.apache.org/docs/2.4/en/mod/directives.html | |||
* ``container`` is what described the `configuration sections` https://httpd.apache.org/docs/2.4/en/sections.html | |||
see examples below for more explanation | |||
Also it includes and enforce some hardening rules to prevent security issues | |||
See `<Hardening.md>`_ and `<apache/hardening-values.yaml>`_. | |||
.. note:: | |||
See the full `Salt Formulas installation and usage instructions | |||
<http://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html>`_. | |||
Available states | |||
================ | |||
.. contents:: | |||
:local: | |||
``apache`` | |||
---------- | |||
Installs the Apache package and starts the service. | |||
``apache.config-ng`` | |||
----------------- | |||
Configures apache server. | |||
The configuration is done by merging the pillar content with defaults | |||
present in the state `<apache/defaults/RedHat/defaults-apache-2.4.yaml>`_ | |||
.. code:: yaml | |||
apache: | |||
server_apache_config: | |||
directives: | |||
- Timeout: 5 | |||
containers: | |||
IfModule: | |||
- | |||
item: 'mime_module' | |||
directives: | |||
- AddType: 'application/x-font-ttf ttc ttf' | |||
- AddType: 'application/x-font-opentype otf' | |||
- AddType: 'application/x-font-woff woff2' | |||
``apache.modules-ng`` | |||
------------------ | |||
Enables and disables Apache modules. | |||
``apache.vhosts.vhost-ng`` | |||
-------------------------- | |||
Configures Apache name-based virtual hosts and creates virtual host directories using data from Pillar. | |||
All necessary data must be provided in the pillar | |||
Exceptions are : | |||
* ``CustomLog`` default is ``/path/apache/log/ServerName-access.log combined`` | |||
* if ``Logformat`` is defined in pillar, ``CustomLog`` is enforced to ``/path/apache/log/ServerName-access.log Logformat`` | |||
* ``ErrorLog`` is enforced to ``/path/apache/log/ServerName-error.log`` | |||
Example Pillar: | |||
Create two vhosts ``example.com.conf`` and ``test.example.com.conf`` | |||
.. code:: yaml | |||
apache: | |||
VirtualHost: | |||
example.com: # <-- this is an id decalaration used in salt and default ServerName | |||
item: '*:80' | |||
directives: | |||
- RewriteEngine: 'on' | |||
- Header: 'set Access-Control-Allow-Methods GET,PUT,POST,DELETE,OPTIONS' | |||
containers: | |||
Location: | |||
item: '/test.html' | |||
directives: | |||
- Require: 'all granted' | |||
site_id_declaration: | |||
item: '10.10.1.1:8080' | |||
directives: | |||
- ServerName: 'test.example.com' | |||
- LogFormat: '"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %{ms}T"' | |||
Files produced by these pillars : | |||
``example.com.conf`` | |||
.. code:: bash | |||
<VirtualHost *:80> | |||
ServerName example.com | |||
CustomLog /var/log/httpd/example.com-access.log combined | |||
ErrorLog /var/log/httpd/example.com-error.log | |||
RewriteEngine on | |||
Header set Access-Control-Allow-Methods GET,PUT,POST,DELETE,OPTIONS | |||
<Location /test.html> | |||
Require all granted | |||
</Location> | |||
</VirtualHost> | |||
``test.example.com.conf`` | |||
.. code:: bash | |||
<VirtualHost 10.10.1.1:8080> | |||
ServerName test.example.com | |||
CustomLog /var/log/httpd/test.example.com-access.log "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %{ms}T" | |||
ErrorLog /var/log/httpd/test.example.com-error.log | |||
</VirtualHost> | |||
this will delete ``test.example.com.conf`` | |||
.. code:: yaml | |||
apache: | |||
VirtualHost: | |||
test.example.com: | |||
item: '10.10.1.1:8080' | |||
absent: True # <-- delete test.example.com.conf | |||
directives: | |||
- ServerName: 'test.example.com' | |||
``apache.uninstall`` | |||
---------- | |||
Stops the Apache service and uninstalls the package. |
@@ -2,6 +2,9 @@ | |||
apache | |||
====== | |||
.. note:: See `<README-ng.rst>`_ for new gen of the state. | |||
Formulas to set up and configure the Apache HTTP server. | |||
.. note:: | |||
@@ -185,14 +188,14 @@ Example Pillar: | |||
When using the provided templates, one can use a space separated list | |||
of interfaces to bind to. For example, to bind both IPv4 and IPv6: | |||
.. code:: yaml | |||
apache: | |||
sites: | |||
example.com: | |||
interface: '1.2.3.4 [2001:abc:def:100::3]' | |||
``apache.manage_security`` | |||
-------------------------- | |||
@@ -0,0 +1,430 @@ | |||
# -*- 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 |
@@ -0,0 +1,118 @@ | |||
{% from "apache/map.jinja" import apache with context %} | |||
{% import_yaml "apache/hardening-values.yaml" as hardening_values %} | |||
{% import_yaml "apache/defaults/" ~ salt['grains.get']('os_family') ~ "/defaults-apache-" ~ apache.version ~ ".yaml" as global_defaults %} | |||
include: | |||
- apache | |||
- apache.mod_ssl | |||
- apache.hardening | |||
{# merge defaults with pillar content #} | |||
{% set pillar_server_config = salt['pillar.get']('apache:server_apache_config', {}) %} | |||
{% set server_config = salt['apache_directives.merge_container_with_additional_data']( | |||
global_defaults.server_apache_config, | |||
pillar_server_config) %} | |||
{# enforce directives values #} | |||
{% for directive, directive_data in hardening_values.enforced_directives.items() %} | |||
{% set server_config = salt['apache_directives.enforce_directive_value'](directive, | |||
directive_data, | |||
container_name='server', | |||
container_data=server_config) %} | |||
{% endfor %} | |||
{# merge server config with hardened sections #} | |||
{% set server_config = salt['apache_directives.enforce_security_directives_into_containers']( | |||
server_config, | |||
hardening_values.enforced_containers ) %} | |||
{# remove containers #} | |||
{% for container_name_to_remove, items_names in hardening_values.containers_to_remove.items() %} | |||
{% for item_name in items_names %} | |||
{% set server_config = salt['apache_directives.remove_container']( | |||
server_config, | |||
container_name_to_remove, | |||
item_name) %} | |||
{% endfor %} | |||
{% endfor %} | |||
{# add supplemental security directives in server configuration #} | |||
{% for d_directive in hardening_values.server_supplemental_directives %} | |||
{% for directive, value in d_directive.items() %} | |||
{% set server_config = salt['apache_directives.append_to_container_directives']( | |||
directive, | |||
value, | |||
server_config) %} | |||
{% endfor %} | |||
{% endfor %} | |||
{% if grains['os_family']=="RedHat" %} | |||
{{ apache.logdir }}: | |||
file.directory: | |||
- makedirs: True | |||
- require: | |||
- pkg: apache | |||
- user: root | |||
- group: {{ apache.group }} | |||
- dir_mode: 750 | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
{{ apache.configfile }}: | |||
file.managed: | |||
- template: jinja | |||
- source: | |||
- salt://apache/files/{{ salt['grains.get']('os_family') }}/apache-{{ apache.version }}-ng.config.jinja | |||
- user: root | |||
- group: root | |||
- mode: 644 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
- context: | |||
apache: {{ apache }} | |||
server_config: {{ server_config | json }} | |||
{{ apache.vhostdir_ng }}: | |||
file.directory: | |||
- makedirs: True | |||
- require: | |||
- pkg: apache | |||
- user: root | |||
- group: root | |||
- dir_mode: 755 | |||
- file_mode: 644 | |||
- recurse: | |||
- user | |||
- group | |||
- mode | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
/etc/httpd/conf.d/welcome.conf: | |||
file.managed: | |||
- source: | |||
- salt://apache/files/{{ salt['grains.get']('os_family') }}/welcome.conf | |||
- user: root | |||
- group: root | |||
- mode: 644 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- service: apache | |||
{% endif %} |
@@ -0,0 +1,77 @@ | |||
# defaults for httpd.conf | |||
# The data structure is a little bit different with pillar structure | |||
# ``directives_multiple`` list are directives that can be present multiple time in conf file | |||
# if the same directive is present in pillar, it will be appended to the defaults ones | |||
# there will be no replacement of values | |||
server_apache_config: | |||
directives: | |||
- ServerRoot: '"/etc/httpd"' | |||
- AllowEncodedSlashes: 'On' | |||
- DocumentRoot: '"/var/www"' | |||
- ServerAdmin: 'root@localhost' | |||
- EnableSendfile: 'on' | |||
- ErrorLog: '"/var/log/httpd/error.log"' | |||
- LogLevel: 'warn core:info' | |||
- AddDefaultCharset: 'UTF-8' | |||
- ServerTokens: 'Prod' | |||
containers: | |||
Directory: | |||
- | |||
item: '/' | |||
directives: | |||
- AllowOverride: 'None' | |||
- Require: 'all denied' | |||
- | |||
item: '/var/www' | |||
directives: | |||
- AllowOverride: 'None' | |||
- Require: 'all granted' | |||
- Options: 'Indexes FollowSymLinks' | |||
- | |||
item: '/var/www/cgi-bin' | |||
directives: | |||
- AllowOverride: 'None' | |||
- Options: 'None' | |||
- Require: 'all granted' | |||
IfModule: | |||
- | |||
item: 'dir_module' | |||
directives: | |||
- DirectoryIndex: index.html | |||
- | |||
item: 'log_config_module' | |||
directives: | |||
- CustomLog: '"/var/log/httpd/access.log" combined' | |||
directives_multiple: # <-- Theses directives are appended as it to pillar content | |||
- LogFormat: '"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined' | |||
- LogFormat: '"%h %l %u %t \"%r\" %>s %b" common' | |||
containers: | |||
IfModule: | |||
- | |||
item: 'logio_module' | |||
directives: | |||
- LogFormat: '"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio' | |||
- | |||
item: 'alias_module' | |||
directives: | |||
- ScriptAlias: '/cgi-bin/ "/var/www/cgi-bin/"' | |||
- | |||
item: 'mime_module' | |||
directives: | |||
- TypesConfig: '/etc/mime.types' | |||
- AddOutputFilter: 'INCLUDES .shtml' | |||
directives_multiple: | |||
- AddType: 'application/x-compress .Z' | |||
- AddType: 'application/x-gzip .gz .tgz' | |||
- AddType: 'text/html .shtml' | |||
- | |||
item: 'mime_magic_module' | |||
directives: | |||
- MIMEMagicFile: 'conf/magic' | |||
FilesMatch: | |||
- | |||
item: '"^\.ht"' | |||
directives: | |||
- Require: 'all denied' |
@@ -0,0 +1,40 @@ | |||
# | |||
# This file is managed by Salt! Do not edit by hand! | |||
# | |||
{%- from "apache/map.jinja" import apache with context %} | |||
{%- import_yaml "apache/hardening-values.yaml" as hardening_values %} | |||
{%- from "apache/lib.sls" import directives_output, container_output with context %} | |||
{%- set list_interfaces_ports = [] %} | |||
{%- for name, vhost in salt['pillar.get']('apache:VirtualHost', {}).items() %} | |||
{%- set items = vhost.item.split() %} | |||
{%- for item in items if item not in list_interfaces_ports %} | |||
{%- do list_interfaces_ports.append(item) %} | |||
{%- endfor %} | |||
{%- endfor %} | |||
{%- for item in list_interfaces_ports %} | |||
Listen {{ item }} | |||
{% else %} | |||
Listen *:80 | |||
{%- endfor %} | |||
{{ directives_output(server_config, 0) }} | |||
Include conf.modules.d/*.conf | |||
User {{ apache.user }} | |||
Group {{ apache.group }} | |||
{%- for container_name, container_data_list in server_config.get('containers', {}).items() %} | |||
{%- for container_data in container_data_list %} | |||
{{ container_output(container_name, container_data) }} | |||
{%- endfor %} | |||
{%- endfor %} | |||
IncludeOptional {{ apache.confdir }}/*.conf | |||
{%- if apache.vhostdir_ng != apache.confdir %} | |||
IncludeOptional {{ apache.vhostdir_ng }}/*.conf | |||
{%- endif %} |
@@ -0,0 +1,15 @@ | |||
# | |||
# This file is managed by Salt! Do not edit by hand! | |||
# | |||
# httpd welcome file commented for security reasons | |||
# This configuration file enables the default ""Welcome"" | |||
# page if there is no default index page present for | |||
# the root URL. To disable the Welcome page, comment | |||
# out all the lines below. | |||
# | |||
##<LocationMatch ""^/+$""> | |||
## Options -Indexes | |||
## ErrorDocument 403 /error/noindex.html | |||
##</LocationMatch> |
@@ -0,0 +1,151 @@ | |||
enforced_directives: | |||
# httpd directives enforced in all configuration files and sections | |||
# data structure : | |||
# directive: | |||
# value: numeric or string - value to enforce | |||
# add_if_absent: False (default) - True -> add it to server configuration if it is absent from pillar | |||
# onlyif_pillar_is: different (default) |greater|lower -> compare numeric values | |||
# - greater : enforce value if the pillar content is > value | |||
# - lower : enforce value if the pillar content is < value | |||
# match : regex | |||
# container : enforce only on the specified container | |||
# regex_group_position : the position of the group to substitute in regex | |||
# values : list of dict - for multiple replacements in the same directive | |||
# Set TimeOut to 10 or less | |||
Timeout: | |||
value: 10 | |||
onlyif_pillar_is: 'greater' | |||
add_if_absent: True | |||
# Set Timeout Limits for Request Headers | |||
RequestReadTimeout: | |||
values: | |||
- | |||
match: '(?<=header=)(\d+-)?(\d+)' | |||
value: 40 | |||
onlyif_pillar_is: 'greater' | |||
regex_group_position: 2 | |||
- | |||
match: '(?<=body=)(\d+-)?(\d+)' | |||
value: 20 | |||
onlyif_pillar_is: 'greater' | |||
regex_group_position: 2 | |||
# Disable the SSL v3.0 Protocol | |||
SSLProtocol: | |||
value: '' | |||
match: '(?<!-)((\+)?SSLv3)' | |||
regex_group_position: 1 | |||
# Minimize Options for Directories to NOT have a value of Includes | |||
Options: | |||
match: '(?<!-)((\+)?Includes)' | |||
value: '' | |||
regex_group_position: 1 | |||
container: 'Directory' | |||
# Set the KeepAlive directive to On | |||
KeepAlive: | |||
value: 'On' | |||
add_if_absent: True | |||
# Set MaxKeepAliveRequests to 100 or greater | |||
MaxKeepAliveRequests: | |||
value: 100 | |||
onlyif_pillar_is: 'lower' | |||
add_if_absent: True | |||
# Set KeepAliveTimeout to 15 or less | |||
KeepAliveTimeout: | |||
value: 15 | |||
onlyif_pillar_is: 'greater' | |||
add_if_absent: True | |||
# Disable HTTP TRACE Method | |||
TraceEnable: | |||
value: 'off' | |||
add_if_absent: True | |||
# Set ServerSignature to 'Off' | |||
ServerSignature: | |||
value: 'off' | |||
add_if_absent: True | |||
# Set ServerToken to 'Prod' | |||
ServerTokens: | |||
value: 'Prod' | |||
# Secure Core Dump Directory | |||
CoreDumpDirectory: | |||
value: '/var/log/httpd' | |||
# Disable SSL Insecure Renegotiation | |||
SSLInsecureRenegotiation: | |||
value: 'off' | |||
# Ensure SSL Compression is not Enabled | |||
SSLCompression: | |||
value: 'off' | |||
# Restrict Override | |||
AllowOverride: | |||
value: 'None' | |||
AllowOverrideList: | |||
value: 'None' | |||
PidFile: | |||
value: '/etc/httpd/run/httpd.pid' | |||
ScoreBoardFile: | |||
value: '/var/run/apache_runtime_status' | |||
SSLHonorCipherOrder: | |||
value: 'On' | |||
enforced_containers: | |||
# httpd sections (containers) enforced in all configuration files and sections | |||
Directory: | |||
# Restrict Override for the OS Root Directory | |||
- | |||
item: '/' | |||
directives: | |||
- AllowOverride: 'None' | |||
- Require: 'all denied' | |||
- Options: 'None' | |||
# Limit HTTP Request Methods | |||
- | |||
item: '/var/www' | |||
directives: | |||
- Options: 'None' | |||
containers: | |||
LimitExcept: | |||
- | |||
item: 'GET POST OPTIONS' | |||
directives: | |||
- Require: 'all denied' | |||
FilesMatch: | |||
# Restrict Access to .ht* files | |||
- | |||
item: '"^\.ht"' | |||
directives: | |||
- Require: 'all denied' | |||
containers_to_remove: | |||
# Remove Default HTML Content | |||
Location: | |||
- '/server-info' | |||
- '/server-status' | |||
- '/perl-status' | |||
server_supplemental_directives: | |||
# httpd directives added as it in httpd.conf | |||
# Restrict HTTP protocol versions | |||
- RewriteEngine: 'On' | |||
- RewriteCond: '%{THE_REQUEST} !HTTP/1\.1$' | |||
- RewriteRule: '.* - [F]' | |||
vhost_supplemental_directives: | |||
# httpd directives added as it in vhost config file | |||
# Inherit server options | |||
- RewriteEngine: 'On' | |||
- RewriteOptions: 'Inherit' | |||
modules: | |||
# httpd modules: enforce enabled and disabled | |||
enforce_disabled: | |||
- "dav" | |||
- "dav_fs" | |||
- "status" | |||
- "autoindex" | |||
- "userdir" | |||
- "info" | |||
enforce_enabled: | |||
- "log_config" | |||
- "reqtimeout" | |||
- "rewrite" |
@@ -0,0 +1,67 @@ | |||
{% from "apache/map.jinja" import apache with context %} | |||
include: | |||
- apache | |||
nologin_shell_for_apache_user: | |||
user.present: | |||
- name: {{ apache.user }} | |||
- shell: /sbin/nologin | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
remove_httpd_manual: | |||
pkg.removed: | |||
- name: httpd-manual | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
/etc/httpd/conf.d/autoindex.conf: | |||
file.managed: | |||
- contents: | | |||
# File commented with Salt, Do NOT Edit | |||
# Do NOT delete because it is contained in the rpm, so it wil re-created on the next upgrade | |||
# It is emptied for hardening purpose | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
/etc/httpd/cgi-bin/printenv: | |||
file.absent: | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
/etc/httpd/cgi-bin/test-cgi: | |||
file.absent: | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache |
@@ -0,0 +1,26 @@ | |||
# macros for conf Files | |||
{%- macro output_indented(mytext, indent_value) %} | |||
{{ mytext | indent(indent_value, true) }} | |||
{%- endmacro %} | |||
{%- macro directives_output(container, col, default_keys = []) -%} | |||
{%- for ordered_directive in container.get('directives', []) -%} | |||
{%- for directive, value in ordered_directive.items() if directive not in default_keys| difference(['LogFormat']) -%} | |||
{{ output_indented(directive + ' ' + value|string, col) }} | |||
{%- endfor %} | |||
{%- endfor %} | |||
{%- endmacro %} | |||
{%- macro container_output(container_name, container_data, col=0, default_directives = []) -%} | |||
{%- set header_text = '<' ~ container_name ~ ' ' ~ container_data.item ~ '>' -%} | |||
{{ output_indented(header_text, col) }} | |||
{{ directives_output(container_data, col+4, default_directives ) }} | |||
{%- for nested_container_name, nested_containers in container_data.get('containers', {}).items() %} | |||
{%- for nested_container in nested_containers %} | |||
{{ container_output(nested_container_name, nested_container, col+4) }} | |||
{%- endfor %} | |||
{%- endfor %} | |||
{%- set footer_text = '</' ~ container_name ~ '>' -%} | |||
{{ output_indented(footer_text, col) }} | |||
{%- endmacro %} |
@@ -0,0 +1,86 @@ | |||
{%- import_yaml "apache/hardening-values.yaml" as hardening_values %} | |||
{% if grains['os_family']=="Debian" %} | |||
include: | |||
- apache | |||
{% for module in salt['pillar.get']('apache:modules:enabled', []) %} | |||
a2enmod {{ module }}: | |||
cmd.run: | |||
- unless: ls /etc/apache2/mods-enabled/{{ module }}.load | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% for module in salt['pillar.get']('apache:modules:disabled', []) %} | |||
a2dismod -f {{ module }}: | |||
cmd.run: | |||
- onlyif: ls /etc/apache2/mods-enabled/{{ module }}.load | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% elif grains['os_family']=="RedHat" %} | |||
include: | |||
- apache | |||
{% for module in salt['pillar.get']('apache:modules:enabled', default=hardening_values.modules.enforce_enabled, merge=True) if module not in hardening_values.modules.enforce_disabled %} | |||
find /etc/httpd/ -name '*.conf' -type f -exec sed -i -e 's/\(^#\)\(\s*LoadModule.{{ module }}_module\)/\2/g' {} \;: | |||
cmd.run: | |||
- unless: httpd -M 2> /dev/null | grep "[[:space:]]{{ module }}_module" | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% for module in salt['pillar.get']('apache:modules:disabled', default=hardening_values.modules.enforce_disabled, merge=True) if module not in hardening_values.modules.enforce_enabled %} | |||
find /etc/httpd/ -name '*.conf' -type f -exec sed -i -e 's/\(^\s*LoadModule.{{ module }}_module\)/#\1/g' {} \;: | |||
cmd.run: | |||
- onlyif: httpd -M 2> /dev/null | grep "[[:space:]]{{ module }}_module" | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% elif salt['grains.get']('os_family') == 'Suse' or salt['grains.get']('os') == 'SUSE' %} | |||
include: | |||
- apache | |||
{% for module in salt['pillar.get']('apache:modules:enabled', []) %} | |||
a2enmod {{ module }}: | |||
cmd.run: | |||
- unless: egrep "^APACHE_MODULES=" /etc/sysconfig/apache2 | grep {{ module }} | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% for module in salt['pillar.get']('apache:modules:disabled', []) %} | |||
a2dismod -f {{ module }}: | |||
cmd.run: | |||
- onlyif: egrep "^APACHE_MODULES=" /etc/sysconfig/apache2 | grep {{ module }} | |||
- order: 225 | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-restart | |||
{% endfor %} | |||
{% endif %} |
@@ -45,6 +45,7 @@ RedHat: | |||
mod_geoip_database: GeoIP | |||
vhostdir: /etc/httpd/vhosts.d | |||
vhostdir_ng: /etc/httpd/conf.d | |||
confdir: /etc/httpd/conf.d | |||
confext: .conf | |||
default_site: default | |||
@@ -111,7 +112,7 @@ FreeBSD: | |||
modulesdir: /usr/local/etc/apache24/modules.d | |||
global_document_root: /usr/local/www/apache24/data | |||
confext: | |||
confext: | |||
default_site: default | |||
default_site_ssl: default-ssl | |||
logdir: /var/log/ |
@@ -0,0 +1,6 @@ | |||
{% from "apache/lib.sls" import container_output with context %} | |||
# | |||
# This file is managed by Salt! Do not edit by hand! | |||
# | |||
{{ container_output('VirtualHost', vhost_data, col=0, default_directives = []) }} |
@@ -0,0 +1,110 @@ | |||
{% from "apache/map.jinja" import apache with context %} | |||
{% import_yaml "apache/hardening-values.yaml" as hardening_values %} | |||
include: | |||
- apache | |||
{% set vhosts = salt['pillar.get']('apache:VirtualHost', {}) %} | |||
{% for virtual_name, vhost in vhosts.items() %} | |||
{% set vhost_server_name = salt['apache_directives.get_directive_single_value']( | |||
'ServerName', | |||
vhost.get('directives'), | |||
default=virtual_name) %} | |||
{% set vhost = salt['apache_directives.enforce_directive_value'](directive='ServerName', | |||
enforced_directive_data={'value': vhost_server_name, | |||
'add_if_absent': True}, | |||
container_name='VirtualHost', | |||
container_data=vhost) %} | |||
{% set default_documentroot = '{0}/{1}'.format(apache.wwwdir, vhost_server_name) %} | |||
{% set documentroot = salt['apache_directives.get_directive_single_value']( | |||
'DocumentRoot', | |||
vhost.get('directives'), | |||
default=default_documentroot) %} | |||
{% set vhost = salt['apache_directives.set_vhost_logging_directives'](vhost, | |||
vhost_server_name, | |||
apache.logdir) %} | |||
# enforce directives values # | |||
{% for directive, directive_data in hardening_values.enforced_directives.items() %} | |||
{% if 'add_if_absent' in directive_data %} | |||
{% do directive_data.update({'add_if_absent': False}) %} | |||
{% endif %} | |||
{% set vhost = salt['apache_directives.enforce_directive_value'](directive, | |||
directive_data, | |||
container_name='VirtualHost', | |||
container_data=vhost) %} | |||
{% endfor %} | |||
# merge vhost config with hardened sections # | |||
{% set vhost = salt['apache_directives.enforce_security_directives_into_containers']( | |||
vhost, | |||
hardening_values.enforced_containers, | |||
add_container=False ) %} | |||
# remove containers # | |||
{% for container_name_to_remove, items_names in hardening_values.containers_to_remove.items() %} | |||
{% for item_name in items_names %} | |||
{% set vhost = salt['apache_directives.remove_container']( | |||
vhost, | |||
container_name_to_remove, | |||
item_name) %} | |||
{% endfor %} | |||
{% endfor %} | |||
# add supplemental security directives in vhost configuration # | |||
{% for d_directive in hardening_values.vhost_supplemental_directives %} | |||
{% for directive, value in d_directive.items() %} | |||
{% set vhost = salt['apache_directives.append_to_container_directives']( | |||
directive, | |||
value, | |||
vhost) %} | |||
{% endfor %} | |||
{% endfor %} | |||
{% if vhost.get('absent', False) %} | |||
{{ vhost_server_name }}: | |||
file.absent: | |||
- name: {{ apache.vhostdir_ng }}/{{ vhost_server_name }}{{ apache.confext }} | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-reload | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
{% else %} | |||
{{ vhost_server_name }}: | |||
file.managed: | |||
- name: {{ apache.vhostdir_ng }}/{{ vhost_server_name }}{{ apache.confext }} | |||
- source: 'salt://apache/vhosts/vhost-ng.conf.jinja' | |||
- template: 'jinja' | |||
- user: root | |||
- group: root | |||
- mode: 644 | |||
- context: | |||
vhost_data: {{ vhost|json }} | |||
- require: | |||
- pkg: apache | |||
- watch_in: | |||
- module: apache-reload | |||
- require_in: | |||
- module: apache-restart | |||
- module: apache-reload | |||
- service: apache | |||
{{ documentroot }}-documentroot: | |||
file.directory: | |||
- name: {{ documentroot }} | |||
- makedirs: True | |||
- allow_symlink: True | |||
{% endif %} | |||
{% endfor %} |
@@ -0,0 +1,122 @@ | |||
# server configuration and any vhost configuration have the same data structure | |||
# This data structure is similar to below : | |||
# | |||
# directives: # list of top level directives/values | |||
# - directive_1: value_1 | |||
# - directive_2: value_2 | |||
# - directive_3: value_3 | |||
# containers: # any type of httpd container | |||
# container_name_1: # Files|Directory|DirectoryMatch|Proxy|location|locationMatch ... | |||
# - | |||
# item: 'path/to/1' # label, path or whatever that container applies to | |||
# directives: # list of directives into this container | |||
# - directive_1: value_1 | |||
# ... | |||
# - | |||
# item: '/path/to/2' | |||
# direcives: | |||
# - ... | |||
# containers: # nested containers in /path/to/2 | |||
# nested_c_1: | |||
# - item: '...' | |||
# directives: | |||
# - ... | |||
# container_name_2: | |||
# - | |||
# item: '...' | |||
# ... | |||
# ``apache`` formula configuration: | |||
apache: | |||
# By default apache restart/reload states run (false skips) | |||
manage_service_states: True | |||
# lookup section overrides ``map.jinja`` values | |||
lookup: | |||
server: apache2 | |||
service: apache2 | |||
user: some_system_user | |||
group: some_system_group | |||
vhostdir: /etc/apache2/sites-available | |||
confdir: /etc/apache2/conf.d | |||
confext: .conf | |||
logdir: /var/log/apache2 | |||
wwwdir: /srv/apache2 | |||
# apache version (generally '2.2' or '2.4') | |||
version: '2.2' | |||
# ``apache.mod_wsgi`` formula additional configuration: | |||
mod_wsgi: mod_wsgi | |||
# global (server) apache directives | |||
server_apache_config: # this content will populate httpd.conf | |||
directives: | |||
- AllowEncodedSlashes: 'On' | |||
- Timeout: 5 | |||
containers: | |||
IfModule: | |||
- | |||
item: 'mime_module' | |||
directives: | |||
- AddType: 'application/x-font-ttf ttc ttf' | |||
- AddType: 'application/x-font-opentype otf' | |||
- AddType: 'application/x-font-woff woff2' | |||
# ``apache.vhosts.vhost-ng`` formula additional configuration: | |||
VirtualHost: | |||
example.com: # <-- site_name : can be the real ServerName or a virtual name | |||
item: '*:8080' # simple example | |||
directives: | |||
- ServerName: 'example.com' # if not defined default is site_name | |||
- ServerAdmin: 'webmaster@example.com' | |||
- DocumentRoot: '/path/to/www/dir/example.com' | |||
- LogLevel: 'warn' | |||
containers: | |||
Location: | |||
- | |||
item: '/test.html' | |||
directives: | |||
- Require: 'all granted' | |||
my_reverse_proxy: # example with a virtual site_name | |||
item: '*:80' # vhost with proxypass | |||
directives: | |||
- ServerName: 'rp-example.com' | |||
- ServerAdmin: 'webmaster@example.com' | |||
- DocumentRoot: '/path/to/www/dir/rp-example.com' | |||
- LogLevel: 'warn' | |||
- ProxyPass: '/ balancer://cluster_1' | |||
- ProxyPassReverse: '/ balancer://cluster_1' | |||
- ProxyPreserveHost: 'On' | |||
containers: | |||
Proxy: | |||
- | |||
item: 'balancer://cluster_1' | |||
directives: | |||
- BalancerMember: 'http://my_backend_1:8081 route=backend-1-8081 timeout=240 retry=120' | |||
- BalancerMember: 'http://my_backend_2:8081 route=backend-2-8081 timeout=240 retry=120' | |||
- ProxySet: 'stickysession=JSESSIONID|jsessionid nofailover=off maxattempts=1' | |||
unused_vhost: | |||
item: '*:80' | |||
absent: True # Delete this vhost | |||
directives: | |||
- ServerName: 'to-delete-example.com' | |||
- ServerAdmin: 'webmaster@example.com' | |||
- DocumentRoot: '/path/to/www/dir/to-delete-example.com' | |||
- LogLevel: 'warn' | |||
containers: | |||
Location: | |||
- | |||
item: '/test.html' | |||
directives: | |||
- Require: 'all granted' | |||
modules: | |||
enabled: # List modules to enable | |||
- ldap | |||
- ssl | |||
disabled: # List modules to disable | |||
- rewrite |
@@ -1,3 +1,5 @@ | |||
# see ``pillar-ng.example.yaml`` for new gen pillar | |||
# ``apache`` formula configuration: | |||
apache: | |||
@@ -98,7 +100,7 @@ apache: | |||
redirectmatch.com: | |||
# Use RedirectMatch Directive https://httpd.apache.org/docs/2.4/fr/mod/mod_alias.html#redirectmatch | |||
# Require module mod_alias | |||
# Require module mod_alias | |||
enabled: True | |||
template_file: salt://apache/vhosts/redirect.tmpl | |||
ServerName: www.redirectmatch.com | |||
@@ -368,4 +370,3 @@ apache: | |||
SSLProtocol: all -SSLv2 -SSLv3 -TLSv1 | |||
SSLHonorCipherOrder: On | |||
SSLOptions: "+StrictRequire" | |||