diff --git a/defaults/main.yml b/defaults/main.yml index c7837fc..79371a1 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,2 +1,80 @@ --- -# defaults file for . + +nginx_apt_package: nginx-full + +# +# Nginx shared variables +# +nginx_root: "/var/www" +nginx_log_dir: '/var/log/nginx' +nginx_ssl_dir: '/etc/nginx/ssl' +nginx_resolver: + hosts: ['208.67.222.222', '208.67.220.220'] # OpenDNS + valid: '300' + timeout: '5' +nginx_error_log_level: 'warn' # http://nginx.org/en/docs/ngx_core_module.html#error_log +nginx_dh_length: 2048 + + +# +# Load upstream +# + +# PHP +nginx_php: false +nginx_php_method: "unix" +nginx_php_unix_sockets: + - "/var/run/php5-fpm.sock" +nginx_php_tcp_sockets: + - host: "127.0.0.1" + port: "15000" + weight: "1" + max_fails: "5" + fail_timeout: "10s" + +# +# Nginx configuration +# +nginx_user: 'www-data' +nginx_worker_processes: '{{ ansible_processor_vcpus }}' +nginx_pid: '/run/nginx.pid' + +nginx_events: + worker_connections: '512' + multi_accept: 'on' + use: 'epoll' + +# +# Nginx HTTP +# +nginx_http: + access_log: 'off' + error_log: 'off' + client_body_buffer_size: '1M' + client_header_buffer_size: '1M' + client_max_body_size: '10M' + large_client_header_buffers: '8 8k' + client_body_timeout: '60' + client_header_timeout: '60' + keepalive_timeout: '30 30' + send_timeout: '120' + ignore_invalid_headers: 'on' + keepalive_requests: '100' + recursive_error_pages: 'on' + sendfile: 'on' + server_name_in_redirect: 'off' + server_tokens: 'off' + tcp_nodelay: 'on' + tcp_nopush: 'on' + reset_timedout_connection: 'on' + gzip: 'on' + gzip_buffers: '16 8k' + gzip_comp_level: '9' + gzip_http_version: '1.0' + gzip_min_length: '0' + gzip_types: 'text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml' + gzip_vary: 'on' + gzip_disable: '"msie6"' +# etag: 'off' + +nginx_vhosts: [] diff --git a/files/index.html b/files/index.html new file mode 100644 index 0000000..ab31912 --- /dev/null +++ b/files/index.html @@ -0,0 +1 @@ +

HTML works

diff --git a/files/index.php b/files/index.php new file mode 100644 index 0000000..22e08b8 --- /dev/null +++ b/files/index.php @@ -0,0 +1,3 @@ +PHP works!"; diff --git a/handlers/main.yml b/handlers/main.yml index 050cdd1..b3cf511 100644 --- a/handlers/main.yml +++ b/handlers/main.yml @@ -1,2 +1,6 @@ --- -# handlers file for . +- name: restart nginx + action: service name=nginx state=restarted enabled=yes + +- name: reload nginx + action: service name=nginx state=reloaded enabled=yes diff --git a/tasks/main.yml b/tasks/main.yml index ace551a..45cc97c 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,2 +1,35 @@ --- -# tasks file for . + +- name: APT | Install nginx + apt: pkg={{ nginx_apt_package }} state=latest update_cache=yes cache_valid_time=3600 + +- name: TEMPLATE | Deploy nginx.conf + template: src=etc/nginx/nginx.conf.j2 dest=/etc/nginx/nginx.conf validate= "nginx -t" + notify: restart nginx + +- name: FILE | Create /etc/nginx/helpers + file: dest=/etc/nginx/helpers owner=root mode=0755 state=directory + +- name: FILE | Create /etc/nginx/ssl + file: dest=/etc/nginx/ssl owner=root mode=0755 state=directory + +#- name: COMMAND | Creates DH file +# command: openssl dhparam -out {{ nginx_dh_path }} {{ nginx_dh_length }} +# args: +# creates: "{{ nginx_dh_path }}" + +- name: TEMPLATE | Deploy all helpers + template: src={{ item }} dest=/etc/nginx/helpers/{{ item | basename | regex_replace('\.j2$','') }} + with_fileglob: '../templates/etc/nginx/helpers/*.j2' + notify: reload nginx + +- name: INCLUDE | PHP configuration + include: php.yml + when: nginx_php + +- name: INCLUDE | Vhosts configuration + include: vhost.yml + +# TODO: +# - Python +# - Ruby (SHIT!) diff --git a/tasks/php.yml b/tasks/php.yml new file mode 100644 index 0000000..4bc91fe --- /dev/null +++ b/tasks/php.yml @@ -0,0 +1,10 @@ +--- + +- name: INCLUDE_VARS | Include PHP defaults vars from PHP role if unspecified + include_vars: ../../php/defaults/main.yml + when: php_fpm_socket is undefined + +- name: TEMPLATE | Deploy PHP upstream (UNIX sockets) to Nginx + template: src=etc/nginx/upstream/php.conf.j2 dest=/etc/nginx/conf.d/php.conf + notify: reload nginx + diff --git a/tasks/vhost.yml b/tasks/vhost.yml new file mode 100644 index 0000000..86093b0 --- /dev/null +++ b/tasks/vhost.yml @@ -0,0 +1,49 @@ +--- + +- name: FILE | Create root folders (foreach nginx_vhosts) + file: path={{ nginx_root }}/{{ item.name }} state=directory recurse=yes owner=www-data group=www-data mode=0755 + file: path={{ nginx_root }}/{{ item.name }}/public state=directory recurse=yes owner=www-data group=www-data mode=0755 + with_items: nginx_vhosts + +- name: TEMPLATE | Create vhosts + template: src=etc/nginx/sites-available/{{ item.template }}.j2 dest=/etc/nginx/sites-available/{{ item.name }} + with_items: nginx_vhosts + notify: reload nginx + +- name: COMMAND | Get sites available + command: ls -1 /etc/nginx/sites-available + register: old_vhosts + changed_when: false + ignore_errors: true + +- name: Delete unmanaged vhosts + file: path=/etc/nginx/sites-enabled/{{ item }} state=absent + file: path=/etc/nginx/sites-available/{{ item }} state=absent + with_items: old_vhosts.stdout_lines + when: item not in nginx_vhosts|map(attribute='name') and item != 'default' + +#- name: COPY | Add index.html / index.php +# copy: src={{ item }} dest={{ nginx_root }}/{{ item.name }}/public/{{ item }} owner=www-data group=www-data mode=0666 +# with_fileglob: "*" + +- name: FILE | Enable vhosts (symlink to sites-enabled) + file: src=/etc/nginx/sites-available/{{ item.name }} dest=/etc/nginx/sites-enabled/{{ item.name }} state=link + with_items: nginx_vhosts + notify: reload nginx + +- name: FILE | Create ssl dir per vhost (if needed) + file: dest=/etc/nginx/ssl/{{ item.name }} owner=root mode=0750 state=directory + with_items: nginx_vhosts + when: item.ssl.use is defined and item.ssl.use + +# TODO... +#- name: COPY | Deploy SSL keys if needed +# copy: src=keys/{{ item.name }}/{{ item.name }}.crt dest=/etc/nginx/ssl/{{ item.name }} mode=660 +# copy: src=keys/{{ item.name }}/{{ item.name }}.key dest=/etc/nginx/ssl/{{ item.name }} mode=660 +# with_items: nginx_vhosts +# when: item.ssl.use and not generatekey + +# TODO: +# - deploy defaults files (index.html/index.php) allready in files/ +# - work with role "ssl_autosign" + diff --git a/templates/etc/nginx/helpers/no-ht.j2 b/templates/etc/nginx/helpers/no-ht.j2 new file mode 100644 index 0000000..d262d38 --- /dev/null +++ b/templates/etc/nginx/helpers/no-ht.j2 @@ -0,0 +1,10 @@ +# +# {{ ansible_managed }} +# + +location ~ /\.ht { + deny all; +} + + +# vim:filetype=nginx diff --git a/templates/etc/nginx/helpers/php.j2 b/templates/etc/nginx/helpers/php.j2 new file mode 100644 index 0000000..addb5db --- /dev/null +++ b/templates/etc/nginx/helpers/php.j2 @@ -0,0 +1,19 @@ +# +# {{ ansible_managed }} +# + +location ~ \.php$ { + fastcgi_pass php; + fastcgi_index index.php; + fastcgi_intercept_errors on; + include fastcgi_params; + + # TODO... + # Newrelic custom header: https://docs.newrelic.com/docs/apm/other-features/request-queueing/request-queue-server-configuration-examples + #fastcgi_param HTTP_X_REQUEST_START "t=${msec}"; + # Newrelic custom PHP appname: https://docs.newrelic.com/docs/agents/php-agent/configuration/php-directory-ini-settings#perdir-nginx + #fastcgi_param PHP_VALUE "newrelic.appname=${host}"; +} + + +# vim:filetype=nginx diff --git a/templates/etc/nginx/helpers/ssl-legacy.j2 b/templates/etc/nginx/helpers/ssl-legacy.j2 new file mode 100644 index 0000000..7464285 --- /dev/null +++ b/templates/etc/nginx/helpers/ssl-legacy.j2 @@ -0,0 +1,18 @@ +# +# {{ ansible_managed }} +# + +ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_session_cache shared:SSL:10m; +add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; +add_header X-Frame-Options DENY; +add_header X-Content-Type-Options nosniff; +ssl_stapling on; +ssl_stapling_verify on; +resolver {{ nginx_resolver.hosts | default(['208.67.222.222', '208.67.220.220']) | join(' ') }} valid={{ nginx_resolver.valid}}s; +resolver_timeout {{ nginx_resolver.timeout }}s; + + +# vim:filetype=nginx diff --git a/templates/etc/nginx/helpers/ssl-strong.j2 b/templates/etc/nginx/helpers/ssl-strong.j2 new file mode 100644 index 0000000..1e5a5c4 --- /dev/null +++ b/templates/etc/nginx/helpers/ssl-strong.j2 @@ -0,0 +1,18 @@ +# +# {{ ansible_managed }} +# + +ssl_ciphers "AES256+EECDH:AES256+EDH"; +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_session_cache shared:SSL:10m; +add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; +add_header X-Frame-Options DENY; +add_header X-Content-Type-Options nosniff; +ssl_stapling on; +ssl_stapling_verify on; +resolver {{ nginx_resolver.hosts | default(['208.67.222.222', '208.67.220.220']) | join(' ') }} valid={{ nginx_resolver.valid}}s; +resolver_timeout {{ nginx_resolver.timeout }}s; + + +# vim:filetype=nginx diff --git a/templates/etc/nginx/helpers/static-expires.j2 b/templates/etc/nginx/helpers/static-expires.j2 new file mode 100644 index 0000000..f553b32 --- /dev/null +++ b/templates/etc/nginx/helpers/static-expires.j2 @@ -0,0 +1,10 @@ +# +# {{ ansible_managed }} +# + +location ~* \.(txt|js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 30d; + log_not_found off; +} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/nginx.conf.j2 b/templates/etc/nginx/nginx.conf.j2 new file mode 100644 index 0000000..50bc444 --- /dev/null +++ b/templates/etc/nginx/nginx.conf.j2 @@ -0,0 +1,30 @@ +# +# {{ ansible_managed }} +# + +user {{ nginx_user }}; +worker_processes {{ nginx_worker_processes }}; +pid {{ nginx_pid }}; + +events { +{% for key, value in nginx_events.iteritems() %} +{{ "\t%-30s %s" | format(key, value) }}; +{% endfor %} +} + +http { + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # From Ansible +{% for key, value in nginx_http.iteritems() %} +{{ "\t%-30s %s" | format(key, value) }}; +{% endfor %} + # /From Ansible + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/sites-available/COMMON.j2 b/templates/etc/nginx/sites-available/COMMON.j2 new file mode 100644 index 0000000..b3e565e --- /dev/null +++ b/templates/etc/nginx/sites-available/COMMON.j2 @@ -0,0 +1,26 @@ +{% if item.ssl is defined and item.ssl.use is defined and item.ssl.use %} + listen {{ item.listen | default('443') }}; +# server_name {{ item.name | join(' ')}}; # TODO: gérer plusieurs vhosts + server_name {{ item.name }}; + ssl on; + ssl_certificate {{ nginx_ssl_dir }}/{{ item.name }}/{{ item.name }}.crt; + ssl_certificate_key {{ nginx_ssl_dir }}/{{ item.name }}/{{ item.name }}.key; + include /etc/nginx/helpers/ssl-{{ item.ssl.template | default('strong') }}; +{% else %} + listen {{ item.listen | default('80') }}; +# server_name {{ item.name | join(' ')}}; # TODO: gérer plusieurs vhosts + server_name {{ item.name }}; +{% endif %} + + root {{ nginx_root }}/{{ item.name }}/public; + +{% if item.use_access_log is defined and item.use_access_log %} + access_log {{ nginx_log_dir }}/{{ item.name }}_access.log combined; +{% else %} + access_log off; +{% endif %} +{% if item.use_error_log is defined and item.use_error_log %} + error_log {{ nginx_log_dir }}/{{ item.name }}_error.log {{ nginx_error_log_level }}; +{% else %} + error_log off; +{% endif %} diff --git a/templates/etc/nginx/sites-available/REDIRECT.j2 b/templates/etc/nginx/sites-available/REDIRECT.j2 new file mode 100644 index 0000000..57dbb4d --- /dev/null +++ b/templates/etc/nginx/sites-available/REDIRECT.j2 @@ -0,0 +1,6 @@ +{% if item.redirect_server_name is defined %} +server { + server_name {{ item.redirect_server_name | join(' ') }}; + return 301 {% if item.ssl.use %}https{% else %}http{% endif %}://{{ item.server_name[0] }}$request_uri; +} +{% endif %} diff --git a/templates/etc/nginx/sites-available/simple-php.j2 b/templates/etc/nginx/sites-available/simple-php.j2 new file mode 100644 index 0000000..d98af9f --- /dev/null +++ b/templates/etc/nginx/sites-available/simple-php.j2 @@ -0,0 +1,19 @@ +# +# {{ ansible_managed }} +# + +server { +{% include 'COMMON.j2' %} + + index index.htm index.html index.php; + location / { + try_files $uri $uri/ /index.php; + } + include /etc/nginx/helpers/php; + include /etc/nginx/helpers/no-ht; + include /etc/nginx/helpers/static-expires; +} + +{% include 'REDIRECT.j2' %} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/sites-available/static.j2 b/templates/etc/nginx/sites-available/static.j2 new file mode 100644 index 0000000..e449c5d --- /dev/null +++ b/templates/etc/nginx/sites-available/static.j2 @@ -0,0 +1,19 @@ +# +# {{ ansible_managed }} +# + +server { +{% include 'COMMON.j2' %} + + index index.htm index.html; + + location / { + try_files $uri $uri/ =404; + } + include /etc/nginx/helpers/static-expires; + include /etc/nginx/helpers/no-ht; +} + +{% include 'REDIRECT.j2' %} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/sites-available/wordpress.j2 b/templates/etc/nginx/sites-available/wordpress.j2 new file mode 100644 index 0000000..3929170 --- /dev/null +++ b/templates/etc/nginx/sites-available/wordpress.j2 @@ -0,0 +1,20 @@ +# +# {{ ansible_managed }} +# + +server { +{% include 'COMMON.j2' %} + + index index.htm index.html index.php; + + location / { + try_files $uri $uri/ /index.php?q=$uri&$args; + } + include /etc/nginx/helpers/static-expires; + include /etc/nginx/helpers/no-ht; + include /etc/nginx/helpers/php; +} + +{% include 'REDIRECT.j2' %} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/upstream/php.conf.j2 b/templates/etc/nginx/upstream/php.conf.j2 new file mode 100644 index 0000000..7f7a85c --- /dev/null +++ b/templates/etc/nginx/upstream/php.conf.j2 @@ -0,0 +1,26 @@ +# +# {{ ansible_managed }} +# + +{% if nginx_php_method == 'unix' %} +upstream php { + {% for item in php_fpm_unix_sockets %} + server unix:{{ item }}; + {% endfor %} +} +{% elif nginx_php_method == 'tcp' %} +upstream php { + {% if nginx_cluster_php_method is defined %} + {{ nginx_cluster_php_method }}; + {% endif %} + {% for item in nginx_cluster_servers %} + server {{ item.host }}:{{ item.port }} weight={{ item.weight | default('100') }} max_fails={{ item.max_fails | default('5') }} fail_timeout={{ item.fail_timeout | default('10s') }}; + {% endfor %} +} +{% else %} +# +# Bad configuration... +# +{% endif%} + +# vim:filetype=nginx diff --git a/templates/etc/nginx/upstream/php_tcp.j2 b/templates/etc/nginx/upstream/php_tcp.j2 new file mode 100644 index 0000000..c31ab21 --- /dev/null +++ b/templates/etc/nginx/upstream/php_tcp.j2 @@ -0,0 +1,14 @@ +# +# {{ ansible_managed }} +# + +upstream php { + {% if nginx_cluster_php_method is defined %} + {{ nginx_cluster_php_method }}; + {% endif %} + {% for item in nginx_cluster_servers %} + server {{ item.host }}:{{ item.port }} weight={{ item.weight | default('100') }} max_fails={{ item.max_fails | default('5') }} fail_timeout={{ item.fail_timeout | default('10s') }}; + {% endfor %} +} + +# vim:filetype=nginx diff --git a/tests/test.yml b/tests/test.yml index a8a2bc0..82ef063 100644 --- a/tests/test.yml +++ b/tests/test.yml @@ -1,5 +1,13 @@ --- - hosts: all + vars: + nginx_vhosts: + - name: 'test.local' + template: 'static' + ssl: + use: false + generatekey: false + template: 'strong' roles: - ../../ diff --git a/vars/main.yml b/vars/main.yml index a38c5fb..9d92874 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,2 +1 @@ ---- -# vars file for . +nginx_dh_path: /etc/nginx/ssl/dhparams.pem