An Ansible role that sets up the Nginx web server on a Proserver.
ansible-proserver-dehydrated is required to manage HTTPS certificates
See ROLE_USAGE.md
Starting with the version 1.1.0, this role is capable of configuring ModSecurity v3 with OWASP Core Rule Set.
The ModSecurity functionality is disabled by default, but can be activated by setting the variable nginx.modsecurity.enabled to yes or true
Default configuration can be seen in the defaults.yaml file.
By default, ModSecurity is configured with the DetectionOnly engine. Security incidents will be logged to the file path set by nginx.modsecurity.config.SecAuditLog, but no actions will be taken.
Setting the dry_run variable to no or false switches the mode to On. In this case, ModSecurity will actually block the requests that it deems malicious.
We recommend running ModSecurity in the DetectionOnly mode and monitoring the logs for false positives for a while, before enabling the blocking functionality.
The configuration options contained in nginx.modsecurity.config mostly follow SpiderLabs' recommended settings
rules lets you define ModSecurity rules to be written to {{ nginx.prefix.modsecurity.config }}/modsecurity.conf. For example:
nginx:
modsecurity:
rules:
import: | # Arbitrary rule name. Will not be used in templates
REQUEST_URI "@beginsWith /import.php" \
"id:1010,\
phase:1,\
pass,\
nolog,\
ctl:ruleRemoveById=920350"Similarly, actions lets you define ModSecurity actions in the following format:
nginx:
modsecurity:
actions:
allow_neos_methods: |
"id:900200,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:'tx.allowed_methods=GET HEAD POST OPTIONS PUT PATCH DELETE'"The default security headers shipped with this role are as follows:
nginx_security_headers_default:
- header: "X-Frame-Options"
value: "SAMEORIGIN"
always: yes
- header: "Permissions-Policy"
value: "camera=(self), display-capture=(self), fullscreen=(self), geolocation=(self), microphone=(self), web-share=(self)"
always: yes
- header: "X-Content-Type-Options"
value: "nosniff"
always: yes
- header: "X-XSS-Protection"
value: "1; mode=block"
always: yes
- header: "Referrer-Policy"
value: "no-referrer-when-downgrade"
always: yes
- header: "Content-Security-Policy"
value: "frame-ancestors 'self'"
always: yes
- header: "Strict-Transport-Security"
value: >-
{% if nginx.hsts %}max-age={{ nginx.hsts.max_age }}{% if nginx.hsts.include_subdomains %};includeSubdomains{% endif %}{% if nginx.hsts.preload %}; preload{% endif %}{% endif %}
always: yesYou can use the nginx.security_headers variable to either overwrite the values of the default headers, or define new ones. The two variables (nginx_security_headers_default and nginx.security_headers) will then be merged and templated to {{ nginx.prefix.config }}/include/security_headers.conf.
nginx:
security_headers:
# Will override the default Content-Security-Policy
- header: "Content-Security-Policy"
value: "frame-ancestors 'self'; frame-src 'self'"
always: yes
# Will be added to the default headers
- header: "Permissions-Policy"
value: "geolocation=(self)"
always: yesnginx.prefix.config: The configuration directory for Nginx. Defaults to /etc/nginx for Linux and /usr/local/etc/nginx for FreeBSD.
nginx.prefix.log: The directory for Nginx logs
nginx:
prefix:
config: >-
{%- if ansible_system == 'Linux' -%}
/etc/nginx
{%- else -%}
/usr/local/etc/nginx
{%- endif -%}
log: /var/log/nginxThe number of Nginx worker processes to be spawned. Defaults to 8
nginx:
worker_processes: 8Specifies the resolvers that Nginx will use. Defaults to Cloudflare's IPv6 public DNS
nginx:
nameserver:
- '[2606:4700:4700::1111]:53'
- '[2606:4700:4700::1001]:53'
nameservers_valid: 300s
nameservers_ipv6: "on"Specifies the max size and bucket size for the server names hash. These parameters have no default values but adjusting them may be useful if you use multiple long domain names and/or get this error when trying to run Nginx:
nginx: [emerg] could not build the server_names_hash, you should increase server_names_hash_bucket_sizeStart with a value of 64 and try increasing the value by a power of 2 if you keep getting the error (e.g. 128, 256, 512, etc.)
nginx:
server_names_hash_max_size:
server_names_hash_bucket_size:Specifies the size of Diffie-Hellman parameters to be generated. The default key size in OpenSSL is 1024 bits, which may be considered insecure by some users. This role sets the default size to 4096. However, a dhparam.pem file with 4096 bits may take a long time to generate (as much as one hour on some hardware). Adjust the size according to your needs.
nginx:
dhparam_bits: 4096Both redirects and moved_permanently specify HTTP/HTTPS redirects in the following format:
nginx:
redirects:
http://example.com: https://www.example.com
https://example.com: https://www.example.comA different format is also available, allowing you to specify the HTTP code of the redirect. The default redirect code is 301.
nginx:
moved_permanently:
http://example.com:
url: https://www.example.com
code: 307redirects and moved_permanently can be used interchangeably.
If your web server is behind a proxy or a load balancer, such as Cloudflare, you will see the Cloudflare proxy IP addresses in your logs, instead of the actual IP addresses of your visitors.
To fix that, you will need to specify the name of the header that actually carries the origin IP address of the request (X-Real-IP by default), as well as the proxy IP addresses that need to be replaced by the original visitors' IPs. For example:
nginx:
real_ip_header: X-Real-IP
set_real_ip_from:
- 192.168.2.1/24
- 192.168.2.2
- 2001:0db8::/32;In case of Cloudflare, an up-to-date list of Cloudflare's IP ranges can be found here
Specifies the CA certificates that will be used to verify upstream TLS. Defaults to /etc/ssl/certs/ca-certificates.crt on Linux and /usr/local/share/certs/ca-root-nss.crt on other operating systems.
nginx:
proxy_set_trusted_certificate >-
{%- if ansible_system == 'Linux' -%}
/etc/ssl/certs/ca-certificates.crt
{%- else -%}
/usr/local/share/certs/ca-root-nss.crt
{%- endif -%}Gives you control over the HSTS policy. The defaults are as follows:
nginx:
hsts:
max_age: 31536000
include_subdomains: no
preload: nolog_format lets you choose between main and json for the log format. Main is the default and is more human-readable out of the box, whereas JSON may be useful for further parsing and processing.
nginx:
log_format: mainlog_formats gives you control over the information written to the logs for each format. By default, only the fields in the JSON format are adjusted.
If you add another format that holds the key fields, it is generated as json
nginx:
log_formats:
json:
fields:
host: $host
remote_addr: $remote_addr
remote_user: $remote_user
time_iso8601: $time_iso8601
request_method: $request_method
request_uri: $request_uri
server_protocol: $server_protocol
status: $status
body_bytes_sent: $body_bytes_sent
http_referer: $http_referer
http_user_agent: $http_user_agent
http_x_forwarded_for: $http_x_forwarded_for
http_x_real_ip: $http_x_real_ip
request_id: $request_id
request_time: $request_time
bytes_sent: $bytes_sent
request_length: $request_length
connection: $connection
connection_requests: $connection_requests
sent_http_content_type: $sent_http_content_type
dnt: $dntIf you want to define a custom non-JSON log_format, you must use the key value instead:
nginx:
log_formats:
apm:
value: '$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for" request_time=$request_time'nginx:
ansible_info:
server_name:
private_api:Provides a way to serve the current inventory_hostname in a JSON format at the domain name specified by nginx.ansible_info.server_name.
The relevant snippet from the http.d/default.conf template is as follows:
location = / {
add_header Content-Type application/json;
return 200 '{"inventory_hostname": "{{ inventory_hostname }}"}';
}If private_api is set, the endpoint will additionally expose group names and all HTTPS endpoints at the location specified by the private_api variable, as shown in the following http.d/default.conf snippet:
{% if nginx.ansible_info.private_api %}
location {{ nginx.ansible_info.private_api }} {
add_header Content-Type application/json;
return 200 '{"inventory_hostname": "{{ inventory_hostname }}", "groups": {{ group_names|to_json }}, "https_endpoints": {{ (dehydrated.domains.keys()|list + dehydrated.domains.values()|sum(start=[]))|to_json }}}';
}
{% endif %}dynamic_modules_path specifies the path to the modules folder. Defaults to /usr/lib/nginx/modules on Linux and /usr/local/libexec/nginx on other operating systems:
nginx:
dynamic_modules_path: >-
{%- if ansible_system == 'Linux' -%}
/usr/lib/nginx/modules
{%- else -%}
/usr/local/libexec/nginx
{%- endif -%}dynamic_modules speficies the modules to be loaded. The ngx_http_headers_more_filter_modules.so is only loaded on Linux, whereas the ngx_stream_module.so is loaded by default on all operating systems:
nginx:
dynamic_modules:
ngx_http_headers_more_filter_module.so: "{{ false if ansible_system == 'Linux' else true }}"
ngx_stream_module.so: noThe httpasswd parameter allows you to specify basic auth credentials and include them in your configurations. By default, no credentials are specified.
Example:
nginx:
htpasswd:
example: # the file name for the htpasswd file
user: passwordThe credentials are provisioned to {{ nginx.prefix.config }}/include and can then be then used in your templates and configuration files as follows:
auth_basic_user_file {{ nginx.prefix.config }}/include/example.htpasswd;nginx:
stub_status_port:If set, serves a simple web page with basic Nginx status data on the specified port.
Adds RFC9116 compliance.
nginx:
security_txt:
Contact:
Expires:
Encryption:
Acknowledgments:
Preferred_Languages: en
Canonical:
Policy:
Hiring:
CSAF:If Contact is set, creates a RFC9116 compliant endpoint.
The security_txt options match the official security.txt options, with the exception of dashes being replaced with underscores due to YAML limitations. The expiration date is set five years ahead of the year in which the role is executed.
This snippet is stored in a dedicated file /etc/nginx/include/security_txt.conf and can be included in your nginx server config like this:
include /etc/nginx/include/security_txt.conf;If set, serves a RFC9116 information under /security.txt and /.well-known/security.txt
You can override or add new mimetypes to nginx. key must be the mimetype, value is a list of values which are combined with spaces. If a mimetype is already present it gets replaced.
nginx:
mimetypes:
font-woff2:
key: "application/font-woff2"
value:
- woff2The previous example generates this line within ./nginx/mime.types
types {
...
application/font-woff2 woff2;
...
}
By default the package 'nginx' will be installed. If you require the smaller version 'nginx-light' you can overwrite the default package name.
nginx:
package: nginx-light