re-organized defaults and site-config, added debugging option

This commit is contained in:
AnsibleGuy 2022-08-11 02:11:32 +02:00
parent 1ac8b14c1d
commit 9f0b889673
14 changed files with 313 additions and 262 deletions

View File

@ -138,3 +138,8 @@ There are also some useful **tags** available:
* sites
* config => configuration (base and instances)
* certs
To debug errors - you can set the 'debug' variable at runtime:
```bash
ansible-playbook -K -D -i inventory/hosts.yml playbook.yml -e debug=yes
```

View File

@ -0,0 +1,11 @@
---
APACHE_HC:
packages: ['systemd', 'apache2']
config_graylist: [
'SSLEngine', 'SSLCertificateKeyFile', 'SSLCertificateFile', 'SSLCertificateChainFile', 'ErrorLog', 'CustomLog', 'ServerAdmin',
'ServerAlias', 'ServerName', 'Redirect'
]
force_removal: false
NONE_VALUES: [none, '', ' ']

View File

@ -1,7 +1,7 @@
---
# default config => is overwritten by provided config
default_apache:
defaults_apache:
sites: {}
ipv6: false # IMPORTANT: at least one ipv6 address MUST BE defined on your system! else apache2 will fail to start
@ -92,79 +92,4 @@ default_apache:
security:
restricted_methods: ['GET', 'POST', 'HEAD']
APACHE_CONFIG: "{{ default_apache | combine(apache, recursive=true) }}"
APACHE_HC:
packages: ['systemd', 'apache2']
# site-specific config
default_site_config:
mode: 'serve'
state: 'present'
admin: 'apache@template.ansibleguy.net'
port_plain: 80
port_ssl: 443
listen: '*'
domain:
aliases: []
ip:
http_versions: [2, 1]
plain_site: true # if http site (only used for redirect to https) should be created
plain_redirect: 'preserve_domain' # keep hostname from plain request => any other value will set the redirect to the main-domain
config: {} # site-specific setting-value pairs
config_additions: [] # lines that will 1-to-1 be appended to the site-config
app_include: false
headers: {}
security: # https://www.nixpal.com/apache-httpd-hardening/
disable_root_index: true
disable_ssi_cgi: true
restrict_methods: true
redirect:
target: 'https://github.com/ansibleguy'
request_uri: false
serve:
path: '/var/www/html'
ssl: # see: https://github.com/ansibleguy/infra_certs
mode: 'ca' # existing/selfsigned/ca/letsencrypt
# existing:
# We expect the certs to be placed in the role's 'files' directory named like the site
# Example: files/certs/ansibleguy.key and files/certs/ansibleguy.crt
# letsencrypt:
# Host needs to have a valid public dns record pointed at it
# Needs to be publicly reachable over port 80/tcp
cert:
name:
cn: 'Apache Certificate'
org: 'AnsibleGuy'
ou:
country:
state:
locality:
email:
crl_distribution: []
ca:
file: # can be used if you want to use an existing ca
cn:
org:
ou:
country:
state:
locality:
email:
pwd: # it's highly recommended setting a passphrase!
letsencrypt:
key_size:
email:
apache_config_graylist: [
'SSLEngine', 'SSLCertificateKeyFile', 'SSLCertificateFile', 'SSLCertificateChainFile', 'ErrorLog', 'CustomLog', 'ServerAdmin',
'ServerAlias', 'ServerName', 'Redirect'
]
force_removal: false
NONE_VALUES: [none, '', ' ']
APACHE_CONFIG: "{{ defaults_apache | combine(apache, recursive=true) }}"

66
defaults/main/2_site.yml Normal file
View File

@ -0,0 +1,66 @@
---
# site-specific config
defaults_site:
mode: 'serve'
state: 'present'
admin: 'apache@template.ansibleguy.net'
port_plain: 80
port_ssl: 443
listen: '*'
domain:
aliases: []
ip:
http_versions: [2, 1]
plain_site: true # if http site (only used for redirect to https) should be created
plain_redirect: 'preserve_domain' # keep hostname from plain request => any other value will set the redirect to the main-domain
config: {} # site-specific setting-value pairs
config_additions: [] # lines that will 1-to-1 be appended to the site-config
app_include: false
headers: {}
security: # https://www.nixpal.com/apache-httpd-hardening/
disable_root_index: true
disable_ssi_cgi: true
restrict_methods: true
redirect:
target: 'https://github.com/ansibleguy'
request_uri: false
serve:
path: '/var/www/html'
ssl: # see: https://github.com/ansibleguy/infra_certs
mode: 'ca' # existing/selfsigned/ca/letsencrypt
# existing:
# We expect the certs to be placed in the role's 'files' directory named like the site
# Example: files/certs/ansibleguy.key and files/certs/ansibleguy.crt
# letsencrypt:
# Host needs to have a valid public dns record pointed at it
# Needs to be publicly reachable over port 80/tcp
cert:
name:
cn: 'Apache Certificate'
org: 'AnsibleGuy'
ou:
country:
state:
locality:
email:
crl_distribution: []
ca:
file: # can be used if you want to use an existing ca
cn:
org:
ou:
country:
state:
locality:
email:
pwd: # it's highly recommended setting a passphrase!
letsencrypt:
key_size:
email:

View File

@ -1,5 +1,21 @@
---
# todo: option for security.txt
- name: "Apache | Debian | Site '{{ name }}' | Showing debug info - user provided config"
ansible.builtin.debug:
var: site_item.value
when:
- debug is defined
- debug
- name: "Apache | Debian | Site '{{ name }}' | Showing debug info - running config"
ansible.builtin.debug:
var: site
when:
- debug is defined
- debug
- name: "Apache | Debian | Site '{{ name }}' | Checking config"
ansible.builtin.fail:
msg: "The required site-configuration was not provided!

View File

@ -1,5 +1,19 @@
---
- name: Apache | Debian | Showing debug info - user provided config
ansible.builtin.debug:
var: apache
when:
- debug is defined
- debug
- name: Apache | Debian | Showing debug info - running config
ansible.builtin.debug:
var: APACHE_CONFIG
when:
- debug is defined
- debug
- name: Apache | Debian | Install apache
ansible.builtin.apt:
name: "{{ APACHE_HC.packages }}"
@ -71,7 +85,7 @@
name: ansibleguy.infra_certs
when: site.ssl.mode == 'letsencrypt'
vars:
site: "{{ default_site_config | combine(site_item.value, recursive=true) }}"
site: "{{ defaults_site | combine(site_item.value, recursive=true) }}"
name: "{{ site_item.key | safe_key }}"
certs:
mode: 'le_certbot'
@ -112,7 +126,7 @@
ansible.builtin.include_tasks: rm_site.yml
when: site.state != 'present'
vars:
site: "{{ default_site_config | combine(site_item.value, recursive=true) }}"
site: "{{ defaults_site | combine(site_item.value, recursive=true) }}"
name: "{{ site_item.key | safe_key }}"
path: "{{ site.serve.path }}"
loop_control:
@ -137,7 +151,7 @@
ansible.builtin.include_tasks: add_site.yml
when: site.state == 'present'
vars:
site: "{{ default_site_config | combine(site_item.value, recursive=true) }}"
site: "{{ defaults_site | combine(site_item.value, recursive=true) }}"
name: "{{ site_item.key | safe_key }}"
path: "{{ site.serve.path }}"
loop_control:

View File

@ -0,0 +1,30 @@
{% if site.plain_site %}
# http listener
<VirtualHost {{ site.listen }}:{{ site.port_plain }}>
ServerName {{ site.domain }}
{% if site.aliases | length > 0 %}
ServerAlias {% for name in site.aliases %} {{ name }} {% endfor %}{% if site.ip is not none %} {{ site.ip }}{% endif %}
{% endif %}
ServerAdmin {{ site.admin }}
# http versions
Protocols {% if 2 in site.http_versions %}h2c {% endif %}{% if 1 in site.http_versions or 2 not in site.http_versions %}http/1.1{% endif %}
{% include "inc/site_http_log.j2" %}
RewriteEngine On
{% if site.ssl.mode != 'letsencrypt' %}
# welcome letsencrypt bots
RewriteCond %{REQUEST_URI} !^\/\.well-known\/acme-challenge\/.*$
{% endif %}
# redirect all to secure connection
{% if site.plain_redirect == 'preserve_domain' %}
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}
{% else %}
RewriteRule ^ https://{{ site.domain }}%{REQUEST_URI}
{% endif %}
</VirtualHost>
{% endif %}

View File

@ -0,0 +1,15 @@
# log config
{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %}
ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}"
CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined
{% elif APACHE_CONFIG.log.syslog %}
ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}"
CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined
{% elif APACHE_CONFIG.log.per_site %}
ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log
CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined
{% else %}
ErrorLog {{ APACHE_CONFIG.log.path }}/error.log
CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined
{% endif %}

View File

@ -0,0 +1,60 @@
# https listener
<VirtualHost {{ site.listen }}:{{ site.port_ssl }}>
ServerName {{ site.domain }}
{% if site.aliases | length > 0 %}
ServerAlias {% for alias in site.aliases %} {{ alias }} {% endfor %}{% if site.ip is not none %} {{ site.ip }}{% endif %}
{% endif %}
ServerAdmin {{ site.admin }}
# http versions
Protocols {% if 2 in site.http_versions %}h2 {% endif %}{% if 1 in site.http_versions or 2 not in site.http_versions %}http/1.1{% endif %}
{% include "inc/site_https_log.j2" %}
{% include "inc/site_https_ssl.j2" %}
{% include "inc/site_https_config.j2" %}
{% include "inc/site_https_headers.j2" %}
{% if site.mode == 'redirect' %}
# redirect-mode config
Redirect permanent / {{ site.redirect.target }}
{% if site.redirect.request_uri %}
RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }}/$1
{% else %}
RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }}
{% endif %}
{% elif site.mode == 'serve' %}
# serve-mode config
DocumentRoot {{ site.serve.path }}
# mode-specific security config
<Directory {{ site.serve.path }}>
{% if site.security.disable_ssi_cgi %}
Options -FollowSymLinks -ExecCGI -Includes
AllowOverride None
{% endif %}
{% if site.security.disable_root_index %}
Options -Indexes
{% endif %}
Require all granted
</Directory>
{% endif %}
{% if site.config_additions | length > 0 %}
# additional lines
{% endif %}
{% for line in site.config_additions %}
{{ line }}
{% endfor %}
{% if site.app_include %}
# additional application config include
IncludeOptional /etc/apache2/sites-available/site_{{ name }}_app.conf
{% endif %}
</VirtualHost>

View File

@ -0,0 +1,32 @@
{% if APACHE_CONFIG.config | length > 0 %}
# global config
{% for setting, value in APACHE_CONFIG.config.items() %}
{% if setting not in APACHE_HC.config_graylist and value not in NONE_VALUES %}
{{ setting }} {{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% if site.config | length > 0 %}
# site-specific config
{% for setting, value in site.config.items() %}
{% if setting not in APACHE_HC.config_graylist and value not in NONE_VALUES %}
{{ setting }} {{ value }}
{% endif %}
{% endfor %}
{% endif %}
# security config
{% if site.security.restrict_methods %}
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(?!{% for method in APACHE_CONFIG.security.restricted_methods %}{{ method }}{% if not loop.last %}|{% endif %}{% endfor %})
RewriteRule .* - [F]
</IfModule>
<Directory />
<LimitExcept {% for method in APACHE_CONFIG.security.restricted_methods %}{{ method }} {% endfor %}>
Require all denied
</LimitExcept>
</Directory>
{% endif %}

View File

@ -0,0 +1,32 @@
{% if APACHE_CONFIG.headers | length > 0 %}
# global headers
<IfModule mod_headers.c>
{% for header, value in APACHE_CONFIG.headers.items() %}
{% if header not in site.headers and value not in NONE_VALUES %}
{% if 'Header' in header %}
{{ header }} {{ value }}
{% else %}
Header set {{ header }} {{ value }}
{% endif %}
{% endif %}
{% endfor %}
</IfModule>
{% endif %}
{% if site.headers | length > 0 %}
# site-specific headers
<IfModule mod_headers.c>
{% for header, value in site.headers.items() %}
{% if value not in NONE_VALUES %}
{% if 'Header' in header %}
{{ header }} {{ value }}
{% else %}
Header set {{ header }} {{ value }}
{% endif %}
{% endif %}
{% endfor %}
</IfModule>
{% endif %}

View File

@ -0,0 +1,15 @@
# log config
{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %}
ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}"
CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}" combined
{% elif APACHE_CONFIG.log.syslog %}
ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}"
CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}" combined
{% elif APACHE_CONFIG.log.per_site %}
ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log
CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined
{% else %}
ErrorLog {{ APACHE_CONFIG.log.path }}/error.log
CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined
{% endif %}

View File

@ -0,0 +1,10 @@
# ssl config
<IfModule mod_ssl.c>
SSLEngine on
SSLCertificateKeyFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}.key
SSLCertificateFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}.crt
{% if site.ssl.mode != 'selfsigned' %}
SSLCertificateChainFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}{% if site.ssl.mode == 'letsencrypt' %}.fullchain{% else %}.chain{% endif %}.crt
{% endif %}
</IfModule>

View File

@ -1,187 +1,7 @@
# {{ ansible_managed }}
# ansibleguy.infra_apache
{% if site.plain_site %}
# http listener
<VirtualHost {{ site.listen }}:{{ site.port_plain }}>
ServerName {{ site.domain }}
{% if site.aliases | length > 0 %}
ServerAlias {% for name in site.aliases %} {{ name }} {% endfor %}{% if site.ip is not none %} {{ site.ip }}{% endif %}
{% endif %}
ServerAdmin {{ site.admin }}
# log config
{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %}
ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}"
CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined
{% elif APACHE_CONFIG.log.syslog %}
ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}"
CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ue }}{{ name }}" combined
{% elif APACHE_CONFIG.log.per_site %}
ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log
CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined
{% else %}
ErrorLog {{ APACHE_CONFIG.log.path }}/error.log
CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined
{% endif %}
# http versions
Protocols {% if 2 in site.http_versions %}h2c {% endif %}{% if 1 in site.http_versions or 2 not in site.http_versions %}http/1.1{% endif %}
# redirect all to secure connection
RewriteEngine On
{% if site.plain_redirect == 'preserve_domain' %}
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI}
{% else %}
RewriteRule ^ https://{{ site.domain }}%{REQUEST_URI}
{% endif %}
</VirtualHost>
{% endif %}
# https listener
<VirtualHost {{ site.listen }}:{{ site.port_ssl }}>
ServerName {{ site.domain }}
{% if site.aliases | length > 0 %}
ServerAlias {% for alias in site.aliases %} {{ alias }} {% endfor %}{% if site.ip is not none %} {{ site.ip }}{% endif %}
{% endif %}
ServerAdmin {{ site.admin }}
# log config
{% if APACHE_CONFIG.log.syslog and APACHE_CONFIG.log.syslog_host is not none %}
ErrorLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.error -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}"
CustomLog "| /usr/bin/logger -n {{ APACHE_CONFIG.log.syslog_host }} -P {{ APACHE_CONFIG.log.syslog_port }} -p local1.info -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}" combined
{% elif APACHE_CONFIG.log.syslog %}
ErrorLog "| /usr/bin/logger -p local1.error -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}"
CustomLog "| /usr/bin/logger -p local1.info -t {{ APACHE_CONFIG.log.prefix_ssl }}{{ name }}" combined
{% elif APACHE_CONFIG.log.per_site %}
ErrorLog {{ APACHE_CONFIG.log.path }}/{{ name }}_error.log
CustomLog {{ APACHE_CONFIG.log.path }}/{{ name }}_access.log combined
{% else %}
ErrorLog {{ APACHE_CONFIG.log.path }}/error.log
CustomLog {{ APACHE_CONFIG.log.path }}/access.log combined
{% endif %}
# http versions
Protocols {% if 2 in site.http_versions %}h2 {% endif %}{% if 1 in site.http_versions or 2 not in site.http_versions %}http/1.1{% endif %}
# ssl config
<IfModule mod_ssl.c>
SSLEngine on
SSLCertificateKeyFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}.key
SSLCertificateFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}.crt
{% if site.ssl.mode != 'selfsigned' %}
SSLCertificateChainFile {{ APACHE_CONFIG.ssl.path }}/{{ name }}{% if site.ssl.mode == 'letsencrypt' %}.fullchain{% else %}.chain{% endif %}.crt
{% endif %}
</IfModule>
{% if APACHE_CONFIG.config | length > 0 %}
# global config
{% for setting, value in APACHE_CONFIG.config.items() %}
{% if setting not in apache_config_graylist and value not in NONE_VALUES %}
{{ setting }} {{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% if site.config | length > 0 %}
# site-specific config
{% for setting, value in site.config.items() %}
{% if setting not in apache_config_graylist and value not in NONE_VALUES %}
{{ setting }} {{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% if APACHE_CONFIG.headers | length > 0 %}
# global headers
<IfModule mod_headers.c>
{% for header, value in APACHE_CONFIG.headers.items() %}
{% if header not in site.headers and value not in NONE_VALUES %}
{% if 'Header' in header %}
{{ header }} {{ value }}
{% else %}
Header set {{ header }} {{ value }}
{% endif %}
{% endif %}
{% endfor %}
</IfModule>
{% endif %}
{% if site.headers | length > 0 %}
# site-specific headers
<IfModule mod_headers.c>
{% for header, value in site.headers.items() %}
{% if value not in NONE_VALUES %}
{% if 'Header' in header %}
{{ header }} {{ value }}
{% else %}
Header set {{ header }} {{ value }}
{% endif %}
{% endif %}
{% endfor %}
</IfModule>
{% endif %}
# security config
{% if site.security.restrict_methods %}
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} ^(?!{% for method in APACHE_CONFIG.security.restricted_methods %}{{ method }}{% if not loop.last %}|{% endif %}{% endfor %})
RewriteRule .* - [F]
</IfModule>
<Directory />
<LimitExcept {% for method in APACHE_CONFIG.security.restricted_methods %}{{ method }} {% endfor %}>
Require all denied
</LimitExcept>
</Directory>
{% endif %}
{% if site.mode == 'redirect' %}
# redirect-mode config
Redirect permanent / {{ site.redirect.target }}
{% if site.redirect.request_uri %}
RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }}/$1
{% else %}
RedirectMatch permanent ^/(.*)$ {{ site.redirect.target }}
{% endif %}
{% elif site.mode == 'serve' %}
# serve-mode config
DocumentRoot {{ site.serve.path }}
# mode-specific security config
<Directory {{ site.serve.path }}>
{% if site.security.disable_ssi_cgi %}
Options -FollowSymLinks -ExecCGI -Includes
AllowOverride None
{% endif %}
{% if site.security.disable_root_index %}
Options -Indexes
{% endif %}
Require all granted
</Directory>
{% endif %}
{% if site.config_additions | length > 0 %}
# additional lines
{% endif %}
{% for line in site.config_additions %}
{{ line }}
{% endfor %}
{% if site.app_include %}
# additional application config include
IncludeOptional /etc/apache2/sites-available/site_{{ name }}_app.conf
{% endif %}
</VirtualHost>
{% include "inc/site_http.j2" %}
{% include "inc/site_https.j2" %}
ServerName {{ site.domain }}