ansible-role-nginx-config icon indicating copy to clipboard operation
ansible-role-nginx-config copied to clipboard

Example of ssl termination with nginx reverse proxy

Open VilleMiekkoja opened this issue 4 years ago • 20 comments

Is your feature request related to a problem? Please describe. Kind of. I wish to terminate ssl with nginx, and simultaneously apply it as reverse proxy. I do not see an example of this in the docs, even I think it is pretty common scenario.

Describe the solution you'd like Just an example configuration on how to terminate ssl with reverse proxy

Describe alternatives you've considered I've considered applying nginx reverse proxy with ssl termination without this role. Like just manually configuring nginx, if it is too hard with this.

VilleMiekkoja avatar May 24 '20 14:05 VilleMiekkoja

You can take the examples and expand them with the dict objects taken from the templates defaults: https://github.com/nginxinc/ansible-role-nginx/blob/master/defaults/main/template.yml

Something like this:

nginx_http_template_enable: true
nginx_http_template:
  jira_mydomain_net:
    conf_file_name: jira_mydomain_net.conf
    servers:
      first:
        listen:
          listen_public:
            ip: 0.0.0.0
            port: 443
            ssl: true
        server_name: jira.mydomain.net
        ssl:
          cert: /etc/ssl/certs/jira.mydomain.net.crt
          key: /etc/ssl/private/jira.mydomain.net.key
        access_log:
          - name: combined
            location: /var/log/nginx/jira.mydomain.net_access.log
        error_log:
          location: /var/log/nginx/jira.mydomain.net_error.log
          level: warn
        reverse_proxy:
          locations:
            default:
              location: /
              proxy_pass: http://jira01.local.mydomain.net:8080

I left out ssl ciphers, dhparam, proxy_set_header etc since I set them in the main config, but you can just take them from the examples or defaults. Same for using upstreams.

xTrekStorex avatar May 24 '20 14:05 xTrekStorex

@xTrekStorex is totally right. At some stage I would also like to have working examples for most use cases covered in Molecule playbooks, including SSL (using self-signed certs) if possible.

alessfg avatar May 25 '20 12:05 alessfg

I am trying to generate this config below with no luck. I wonder if its supported.

location /app1/ { proxy_pass http://localhost:6000; } location /app2/ { proxy_pass http://localhost:5000; }

vietcgi avatar Mar 04 '21 22:03 vietcgi

Should definitely work. Are you using the latest release or main? If on main, check https://github.com/nginxinc/ansible-role-nginx-config/blob/main/molecule/default/converge.yml#L153-L290 -- you'll see there's two location blocks with proxy_pass. You can delete all the other variables that are being tested and set the values you mentioned for location and proxy_pass. (Side note: might want to create a new issue, your question is not related to ssl as far as I can tell.)

alessfg avatar Mar 05 '21 01:03 alessfg

I am also unable to configure a simple reverse proxy. In the example above I don't recognize the

reverse_proxy:

section.

When I try to use proxy_pass instead of root on a server that I have defined I get

TemplateAssertionError: no test named 'boolean'

A working example would be appreciated.

ghomem avatar Mar 23 '21 22:03 ghomem

The reverse_proxy section no longer exists. It got refactored into the proxy dictionaries you can now find on main (and 0.4.0). Did you check the molecule examples in the previous comment? In addition, proxy_pass is not allowed within a server context. It always has to be inside a location https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass.

alessfg avatar Mar 23 '21 22:03 alessfg

Example:

nginx_config_http_template_enable: true
    nginx_config_http_template:
      - template_file     : http/default.conf.j2
        conf_file_name    : '{{ my_server_name }}.conf'
        conf_file_location: /etc/nginx/conf.d/
        servers:
          - listen:
              - port: 443
                ssl : true
                opts: []
            server_name: '{{ my_server_name }}'
            ssl:
              cert                 : '/etc/ssl/certs/{{ my_ssl_prefix }}.nginx.bundle.crt'
              key                  : '/etc/ssl/private/{{ my_ssl_prefix }}.key'
              protocols            : '{{ NGINX_PROTOCOLS }}'
              prefer_server_ciphers: true
              ciphers              : '{{ NGINX_CIPHERS   }}'
              stapling             : true
              stapling_verify      : true
            autoindex : false
            locations :
              - location: /
                root    : '{{ my_root_folder }}'
              - location: /backend
                proxy_pass: http://127.0.0.1:8080/
                proxy:
                  set_header:
                     - field: Host
                       value: $host
                     - field: X-Real-IP
                       value: $remote_addr
                     - field: X-Forwarded-For
                       value: $proxy_add_x_forwarded_for
                     - field: X-Forwarded-Proto
                       value: $scheme

This results in

failed: [testing-snap05.MYDOMAIN.TLD] (item={'template_file': 'http/default.conf.j2', 'conf_file_name': 'testing-snap05.staging.MYDOMAIN.TLD.conf', 'conf_file_location': '/etc/nginx/conf.d/', 'servers': [{'listen': [{'port': 443, 'ssl': True, 'opts': []}], 'server_name': 'testing-snap05.staging.MYDOMAIN.TLD', 'ssl': {'cert': '/etc/ssl/certs/star.staging.MYDOMAIN.TLD.nginx.bundle.crt', 'key': '/etc/ssl/private/star.staging.MYDOMAIN.TLD.key', 'protocols': 'TLSv1.3', 'prefer_server_ciphers': True, 'ciphers': 'HIGH:!aNULL:!MD5', 'stapling': True, 'stapling_verify': True}, 'autoindex': False, 'locations': [{'location': '/', 'root': '/usr/share/nginx/html'}, {'location': '/backend', 'proxy_pass': 'http://127.0.0.1:8080/', 'proxy': {'set_header': [{'field': 'Host', 'value': '$host'}, {'field': 'X-Real-IP', 'value': '$remote_addr'}, {'field': 'X-Forwarded-For', 'value': '$proxy_add_x_forwarded_for'}, {'field': 'X-Forwarded-Proto', 'value': '$scheme'}]}}]}]}) => {"ansible_loop_var": "item", "changed": false, "item": {"conf_file_location": "/etc/nginx/conf.d/", "conf_file_name": "testing-snap05.staging.MYDOMAIN.TLD.conf", "servers": [{"autoindex": false, "listen": [{"opts": [], "port": 443, "ssl": true}], "locations": [{"location": "/", "root": "/usr/share/nginx/html"}, {"location": "/backend", "proxy": {"set_header": [{"field": "Host", "value": "$host"}, {"field": "X-Real-IP", "value": "$remote_addr"}, {"field": "X-Forwarded-For", "value": "$proxy_add_x_forwarded_for"}, {"field": "X-Forwarded-Proto", "value": "$scheme"}]}, "proxy_pass": "http://127.0.0.1:8080/"}], "server_name": "testing-snap05.staging.MYDOMAIN.TLD", "ssl": {"cert": "/etc/ssl/certs/star.staging.MYDOMAIN.TLD.nginx.bundle.crt", "ciphers": "HIGH:!aNULL:!MD5", "key": "/etc/ssl/private/star.staging.MYDOMAIN.TLD.key", "prefer_server_ciphers": true, "protocols": "TLSv1.3", "stapling": true, "stapling_verify": true}}], "template_file": "http/default.conf.j2"}, "msg": "TemplateAssertionError: no test named 'boolean'"}

ghomem avatar Mar 23 '21 22:03 ghomem

Right. Try to update to the latest version of Jinja2. The templates require Jinja2 2.11.x to properly work. This is most likely related to the errors you were finding on https://github.com/nginxinc/ansible-role-nginx-config/issues/94.

alessfg avatar Mar 24 '21 00:03 alessfg

Thank you. Does it exist as a package for Ubuntu 20.04? Otherwise what is the recommended update method?

ghomem avatar Mar 24 '21 00:03 ghomem

Jinja2 is a Python package. You can try running pip install -U Jinja2. You can read more here https://jinja.palletsprojects.com/en/2.11.x/intro/#installation

alessfg avatar Mar 24 '21 00:03 alessfg

Hello,

Having the same issue on python 2.7 and ansible 2.9.6: "TemplateAssertionError: no test named 'boolean'"

On python 3.6 and ansible 2.9.6 everything works fine.

Variables:

nginx_config_http_template_enable: true
nginx_config_http_template:
  - template_file: http/default.conf.j2
    conf_file_name: 50_example.com.conf
    conf_file_location: /etc/nginx/conf.d/

    servers:
      - listen:
          - ip: 0.0.0.0
            port: 80

          - ip: 0.0.0.0
            port: 443
            ssl: true
        
        ssl:    
          cert: /etc/pki/tls/certs/example.com.crt
          key: /etc/pki/tls/private/example.com.key

        server_name: example.com
        error_page: /usr/share/nginx/html
        autoindex: false
        http_demo_conf: false

        access_log:
          - name: json
            location: /var/log/nginx/example.com-access.json.log
        error_log:
          level: warn
          location: /var/log/nginx/example.com-error.log

        locations:
          - location: /
            proxy_pass: http://127.0.0.1
            proxy:
              bind: false
              set_header:
                - field: Host
                  value: $host
                - field: X-Forwarded-For
                  value: $proxy_add_x_forwarded_for
                - field: X-Real-IP
                  value: $remote_addr
                - field: REMOTE_ADDR
                  value: $remote_addr

Results to:

+#
+# Ansible managed
+#
+
+
+
+
+server {
+    listen 0.0.0.0:80;
+    listen 0.0.0.0:443 ssl;
+    server_name example.com;
+    ssl_certificate /etc/pki/tls/certs/example.com.crt;
+    ssl_certificate_key /etc/pki/tls/private/example.com.key;
+    location / {
+        proxy_bind off;
+        proxy_set_header Host $host;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header REMOTE_ADDR $remote_addr;
+
+        proxy_pass http://127.0.0.1;
+
+    }
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+    access_log  /var/log/nginx/example.com-access.json.log  json;
+    error_log /var/log/nginx/example.com-error.log warn;
+}

pavelkim avatar Apr 15 '21 06:04 pavelkim

Try updating Jinja2. You are not using the latest release (v2.11).

alessfg avatar Apr 15 '21 10:04 alessfg

@alessfg Thanks!

There was no red output, so I didn't notice that I'm actually having troubles updating Jinja2 locally:

bash-3.2$ pip install -U Jinja2
Collecting Jinja2
  Using cached https://files.pythonhosted.org/packages/7e/c2/1eece8c95ddbc9b1aeb64f5783a9e07a286de42191b7204d67b7496ddf35/Jinja2-2.11.3-py2.py3-none-any.whl
Requirement not upgraded as not directly required: MarkupSafe>=0.23 in /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages (from Jinja2) (0.23)
Installing collected packages: Jinja2
  Found existing installation: Jinja2 2.10.1
    Uninstalling Jinja2-2.10.1:
      Successfully uninstalled Jinja2-2.10.1
  Rolling back uninstall of Jinja2

You are using pip version 10.0.0, however version 20.3.4 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

In a clean virtualenv with Python 2.7 and Jinja2 2.11.3 everything works fine:

(py27) bash-3.2$ pip freeze 
ansible==2.9.6
cffi==1.14.5
cryptography==3.3.2
enum34==1.1.10
ipaddress==1.0.23
Jinja2==2.11.3
MarkupSafe==1.1.1
pycparser==2.20
PyYAML==5.4.1
six==1.15.0

pavelkim avatar Apr 15 '21 13:04 pavelkim

Just for anyone looking for an currently working example: v 0.4.0

  - template_file: http/default.conf.j2
    deployment_location: /etc/nginx/conf.d/name.conf
    config:

      upstreams:
        - name: name_example_com
          least_conn: true
          servers:
            - address: '172.16.20.55:80'
              weight: 5

      servers:
        - core:
            listen:
              - port: 80
            server_name:
              - name.example.com

          locations:
            - location: '/robots.txt'
              log_not_found: false  # wont get set
              access:
                allow: 'all'
              log:
                access: false
            - location: '/'
              rewrite:
                return:
                  code: 301
                  url: 'https://$server_name:443$request_uri'


        - core:
            listen:
              - port: 443
                ssl: true
                http2: true
            server_name:
              - name.example.com

          ssl:
            certificate:
              - /etc/pki/tls/certs/name.example.com-rsa/name.example.com-rsa-fullchain.pem
              - /etc/pki/tls/certs/name.example.com-p-384/name.example.com-p-384-fullchain.pem
            certificate_key:
              - /etc/pki/tls/private/name.example.com-rsa/name.example.com-rsa.key
              - /etc/pki/tls/private/name.example.com-p-384/name.example.com-p-384.key
            trusted_certificate: '/etc/pki/tls/certs/name.example.com-p-384/name.example.com-p-384-chain.pem'
            dhparam: '/etc/pki/tls/misc/dhparam.crt'
            protocols:
              - TLSv1.2
              - TLSv1.3
            ciphers:
              - ECDHE-ECDSA-AES128-GCM-SHA256
              - ECDHE-RSA-AES128-GCM-SHA256
              - ECDHE-ECDSA-AES256-GCM-SHA384
              - ECDHE-RSA-AES256-GCM-SHA384
              - ECDHE-ECDSA-CHACHA20-POLY1305
              - ECDHE-RSA-CHACHA20-POLY1305
              - DHE-RSA-AES128-GCM-SHA256
              - DHE-RSA-AES256-GCM-SHA384
            ecdh_curve:
              - secp521r1
              - secp384r1
            prefer_server_ciphers: false
#            stapling: true
#            stapling_verify: true
            session_cache:
              shared:
                name: 'SSL'
                size: '10m'
            session_tickets: true
            session_timeout: '1d'

          access:
            allow:
              - 172.16.0.0/12
              - 10.255.255.0/24
            deny: 'all'

          headers:
            add_headers:
              - name: 'Strict-Transport-Security'
                value: '"max-age=63072000"'
                always: true
              - name: 'X-Frame-Options'
                value: '"SAMEORIGIN"'
                always: false
            add_trailers:
              - name: 'Strict-Transport-Security'
                value: '"max-age=63072000"'
                always: true
              - name: 'X-Frame-Options'
                value: '"SAMEORIGIN"'
                always: false

          log:
            access:
              - path: '/var/log/nginx/name_access.log'
                format: 'main'
            error:
              - /var/log/nginx/name_error.log

          locations:
            - location: '/'
              proxy:
                pass: 'http://name_example_com'
                connect_timeout: '1s'
                bind: false
                set_header:
                  - field: 'Host'
                    value: '$host'
                  - field: 'X-Forwarded-For'
                    value: '$proxy_add_x_forwarded_for'
                  - field: 'X-Real-IP'
                    value: '$remote_addr'
                  - field: 'REMOTE_ADDR'
                    value: '$remote_addr'

Kariton avatar Nov 13 '21 02:11 Kariton

But how do i get an nested location block like this: can anyone help me out? (is that even the best way do configure such location blocks?)

    location ^~ /some/dir/ {
        location ~ \.json$ {
          if ($http_referer != "") {
            return 301 https://app.example.com:443/#HTTP_request('GET','https://$server_name:443$request_uri','','Cross-Origin%20Resource%20Sharing',false);
          }
          proxy_bind off;
          proxy_connect_timeout 1s;
          proxy_pass http://data_example_com;
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header REMOTE_ADDR $remote_addr;

          allow 172.16.0.0/12;
          allow 10.255.255.0/24;
          deny all;
        }

        proxy_bind off;
        proxy_connect_timeout 1s;
        proxy_pass http://data_example_com;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE_ADDR $remote_addr;

        allow 172.16.0.0/12;
        allow 10.255.255.0/24;
        deny all;
    }

Kariton avatar Nov 13 '21 02:11 Kariton

The role in its current iteration does not allow for nested location blocks. You can probably achieve what you want with a little bit of code duplication by creating two different location blocks. Alternatively, and given that you are also using an if statement (which is also not supported by the role), maybe check out the custom_directives parameter? You could potentially include your whole nested location block using that parameter.

alessfg avatar Nov 13 '21 03:11 alessfg

Thanks for that reply.

I already utilize custom_directives for map and geo type of configuration.

But i dont came up with the idea to use them for the entire location block.

This did the trick! THANKS! :)

Kariton avatar Nov 13 '21 04:11 Kariton

Just for anyone looking for an currently working example: v 0.4.0

That's pretty useful!

I feel there's truly a need to have a simple sample configuration here. I tried the above and it just did zlit here. I have the following playbook:

---
- name: Prometheus installation
  hosts: prometheus
  tasks:
    - name: Install NGINX
      ansible.builtin.include_role:
        name: nginxinc.nginx
      vars:
        nginx_manage_repo: false
        nginx_install_from: "os_repository"
    - name: Configure NGINX
      ansible.builtin.include_role:
        name: nginxinc.nginx_core.nginx_config
      vars:
        nginx_config_debug_output: false
        nginx_config_cleanup: false
        nginx_config_http_template_enable: true
        nginx_config_http_template:
          - template_file: http/default.conf.j2
            conf_file_name: prometheus.debian.net.conf
            conf_file_location: /etc/nginx/conf.d/
            backup: true
            servers:
              - core:
                  listen:
                    - port: 80
                    - port: 443
                      ssl: true
                  server_name:
                    - prometheus.debian.net
                
                ssl:
                  certificate:
                    - /etc/letsencrypt/live/prometheus.debian.net/fullchain.pem
                  certificate_key:
                    - /etc/letsencrypt/live/prometheus.debian.net/privkey.pem
        
                server_name: prometheus.debian.net
                error_page: /usr/share/nginx/html
                autoindex: false
                http_demo_conf: false
                log:
                  access:
                    - path: /var/log/nginx/prometheus.debian.net_access.log
                      format: combined
                  error:
                    - /var/log/nginx/prometheus.debian.net_error.log

                locations:
                  - location: /
                    proxy:
                      pass: http://localhost:9090

this doesn't even create a prometheus.debian.net.conf file anywhere I could find. I've been pouring through https://github.com/nginxinc/ansible-role-nginx-config/blob/main/molecule/plus/converge.yml and https://github.com/nginxinc/ansible-role-nginx-config/blob/main/molecule/default/converge.yml and those are very verbose, it's quite hard to find my way in there.

So could we please have an up-to-date, minimalist configuration people could derive from? It's really nice to have the full details of all the possible configuration items, but we get totally lost in that YAML file there...

anarcat avatar Sep 28 '23 00:09 anarcat

actually, this works now:

---
- name: Prometheus installation
  hosts: prometheus
  tasks:
    - name: Install NGINX
      ansible.builtin.include_role:
        name: nginxinc.nginx
      vars:
        nginx_manage_repo: false
        nginx_install_from: "os_repository"
    - name: Configure NGINX
      ansible.builtin.include_role:
        name: nginxinc.nginx_core.nginx_config
      vars:
        nginx_config_http_template_enable: true
        nginx_config_http_template:
          - template_file: http/default.conf.j2
            deployment_location: /etc/nginx/conf.d/prometheus.debian.net.conf
            config:
              servers:
                - core:
                    listen:
                      - port: 80
                      - port: 443
                        ssl: true
                    server_name:
                      - prometheus.debian.net
                  
                  ssl:
                    certificate:
                      - /etc/letsencrypt/live/prometheus.debian.net/fullchain.pem
                    certificate_key:
                      - /etc/letsencrypt/live/prometheus.debian.net/privkey.pem
          
                  server_name: prometheus.debian.net
                  log:
                    access:
                      - path: /var/log/nginx/prometheus.debian.net_access.log
                        format: combined
                    error:
                      - /var/log/nginx/prometheus.debian.net_error.log
  
                  locations:
                    - location: /
                      proxy:
                        pass: http://localhost:9090

that's as minimal as it gets, and i think that something like this should be one of an example directly linked from the readme, if not directly in the readme file...

anarcat avatar Sep 28 '23 00:09 anarcat

I agree! Working on/improving how some use cases are documented is one of the next items in my bucket list (I want to adapt the model used in the core nginx role). I can't promise an exact date but hopefully it'll be within the next month 😁

alessfg avatar Sep 28 '23 15:09 alessfg