├── .circleci └── config.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── defaults └── main.yml ├── meta └── main.yml ├── tasks └── main.yml ├── templates └── vhost.conf └── tests ├── ansible.cfg ├── hosts ├── requirements.yml └── test.yml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | # https://circleci.com/docs/2.0/local-jobs/#relative-path-for-working_directory 5 | working_directory: /etc/gsa.https-proxy 6 | docker: 7 | - image: docker 8 | steps: 9 | - checkout 10 | - setup_remote_docker 11 | - run: 12 | name: Start remote container 13 | command: docker run -it --privileged -d --name remote centos/systemd 14 | - run: 15 | name: Install Ansible 16 | command: apk add --update-cache ansible 17 | - run: 18 | name: Install Ansible roles 19 | command: cd tests && ansible-galaxy install -r requirements.yml 20 | - run: 21 | name: Run Ansible playbook 22 | command: cd tests && ansible-playbook -i hosts test.yml 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tests/roles/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Welcome! 2 | 3 | We're so glad you're thinking about contributing to an 18F open source project! If you're unsure about anything, just ask -- or submit the issue or pull request anyway. The worst that can happen is you'll be politely asked to change something. We love all friendly contributions. 4 | 5 | We want to ensure a welcoming environment for all of our projects. Our staff follow the [18F Code of Conduct](https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md) and all contributors should do the same. 6 | 7 | We encourage you to read this project's CONTRIBUTING policy (you are here), its [LICENSE](LICENSE.md), and its [README](README.md). 8 | 9 | If you have any questions or want to read more, check out the [18F Open Source Policy GitHub repository](https://github.com/18f/open-source-policy), or just [shoot us an email](mailto:18f@gsa.gov). 10 | 11 | ## Public domain 12 | 13 | This project is in the public domain within the United States, and 14 | copyright and related rights in the work worldwide are waived through 15 | the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/). 16 | 17 | All contributions to this project will be released under the CC0 18 | dedication. By submitting a pull request, you are agreeing to comply 19 | with this waiver of copyright interest. 20 | 21 | ## Local development 22 | 23 | Tests can be run locally with the [CircleCI CLI](https://circleci.com/docs/2.0/local-jobs/). 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | As a work of the United States government, this project is in the 2 | public domain within the United States. 3 | 4 | Additionally, we waive copyright and related rights in the work 5 | worldwide through the CC0 1.0 Universal public domain dedication. 6 | 7 | ## CC0 1.0 Universal summary 8 | 9 | This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode). 10 | 11 | ### No copyright 12 | 13 | The person who associated a work with this deed has dedicated the work to 14 | the public domain by waiving all rights to the work worldwide 15 | under copyright law, including all related and neighboring rights, to the 16 | extent allowed by law. 17 | 18 | You can copy, modify, distribute and perform the work, even for commercial 19 | purposes, all without asking permission. 20 | 21 | ### Other information 22 | 23 | In no way are the patent or trademark rights of any person affected by CC0, 24 | nor are the rights that other persons may have in the work or in how the 25 | work is used, such as publicity or privacy rights. 26 | 27 | Unless expressly stated otherwise, the person who associated a work with 28 | this deed makes no warranties about the work, and disclaims liability for 29 | all uses of the work, to the fullest extent permitted by applicable law. 30 | When using or citing the work, you should not imply endorsement by the 31 | author or the affirmer. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Proxy Role for Ansible [![CircleCI](https://circleci.com/gh/GSA/ansible-https-proxy.svg?style=svg)](https://circleci.com/gh/GSA/ansible-https-proxy) 2 | 3 | Ansible role to set up nginx as a secure proxy. The primary use case is HTTPS termination for another service that can't do so on its own. 4 | 5 | ## Requirements 6 | 7 | None. 8 | 9 | ## Role variables 10 | 11 | Required variables: 12 | 13 | * `external_hostname` - the external URL of this proxy 14 | * `upstream_origin` - the internal hostname + port (if not 80) being proxied to 15 | * [SSL configuration](https://github.com/jdauphant/ansible-role-ssl-certs#examples) 16 | * Storing [key data](https://github.com/jdauphant/ansible-role-ssl-certs#example-to-deploy-a-ssl-certificate-stored-in-variables) in a Vault is the recommended approach, though you can use the other options. 17 | 18 | ## Dependencies 19 | 20 | * [`geerlingguy.nginx`](https://galaxy.ansible.com/geerlingguy/nginx/) 21 | * [`geerlingguy.repo-epel`](https://galaxy.ansible.com/geerlingguy/repo-epel/) 22 | * [`jdauphant.ssl-certs`](https://galaxy.ansible.com/jdauphant/ssl-certs/) 23 | 24 | ## Example usage 25 | 26 | ```yaml 27 | # requirements.yml 28 | - name: gsa.https-proxy 29 | src: https://github.com/GSA/ansible-https-proxy 30 | 31 | # group_vars/https_proxy/vars.yml 32 | external_hostname: secure.site.gov 33 | upstream_origin: 127.0.0.1:8080 34 | ssl_certs_local_cert_data: "{{ vault_ssl_certs_local_cert_data }}" 35 | ssl_certs_local_privkey_data: "{{ vault_ssl_certs_local_privkey_data }}" 36 | 37 | # group_vars/https_proxy/vault.yml (encrypted) 38 | vault_ssl_certs_local_cert_data: | 39 | -----BEGIN CERTIFICATE----- 40 | ... 41 | -----END CERTIFICATE----- 42 | vault_ssl_certs_local_privkey_data: | 43 | -----BEGIN RSA PRIVATE KEY----- 44 | ... 45 | -----END RSA PRIVATE KEY----- 46 | 47 | # playbooks/https_proxy.yml 48 | - hosts: https_proxy 49 | become: true 50 | roles: 51 | - gsa.https-proxy 52 | ``` 53 | 54 | ## License 55 | 56 | CC0 57 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # need to specify both here and in `../meta/main.yml`, due to https://github.com/ansible/ansible/issues/17678 3 | ssl_certs_common_name: "{{ external_hostname }}" 4 | 5 | # a space-delimited list of server names that nginx should respond to 6 | # https://nginx.org/en/docs/http/server_names.html 7 | server_names: "{{ external_hostname }}" 8 | 9 | nginx_proxy_vhost_path: "{{ nginx_vhost_path }}/https_proxy.conf" 10 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: Aidan Feldman 3 | description: Set up nginx as a secure proxy 4 | company: General Services Administration 5 | 6 | license: CC0 7 | 8 | min_ansible_version: 1.2 9 | 10 | platforms: 11 | - name: EL 12 | versions: 13 | - 7 14 | 15 | galaxy_tags: 16 | - nginx 17 | - secure 18 | - proxy 19 | - https 20 | - tls 21 | - ssl 22 | - termination 23 | 24 | # must match `../tests/requirements.yml` 25 | dependencies: 26 | 27 | - role: geerlingguy.repo-epel 28 | 29 | - role: geerlingguy.nginx 30 | nginx_remove_default_vhost: true 31 | nginx_server_tokens: "off" 32 | # use EPEL version 33 | nginx_yum_repo_enabled: false 34 | nginx_upstreams: 35 | - name: http_upstream 36 | servers: 37 | - "{{ upstream_origin }} fail_timeout=0" 38 | 39 | - role: jdauphant.ssl-certs 40 | # need to specify both here and in `../defaults/main.yml`, due to https://github.com/ansible/ansible/issues/17678 41 | ssl_certs_common_name: "{{ external_hostname }}" 42 | # match geerlingguy.nginx 43 | ssl_certs_path_owner: "{{ nginx_user }}" 44 | ssl_certs_path_group: "{{ nginx_user }}" 45 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install seboolean dependencies 3 | yum: 4 | name: 5 | - libselinux-python 6 | - libsemanage-python 7 | 8 | # TODO only do this if the upstream_origin is local 9 | - name: Enable network connections within the machine 10 | seboolean: 11 | name: httpd_can_network_connect 12 | state: yes 13 | persistent: yes 14 | # https://github.com/ansible/ansible/issues/18692 15 | when: ansible_selinux is defined and ansible_selinux != False and ansible_selinux.status == 'enabled' 16 | 17 | # manage our own vhost file, because a) the template doesn't work for us, and b) we can use variables from `jdauphant.ssl-certs` 18 | - name: Add vhost config file 19 | template: 20 | src: vhost.conf 21 | dest: "{{ nginx_proxy_vhost_path }}" 22 | owner: root 23 | group: root 24 | mode: 0644 25 | notify: 26 | - validate nginx configuration 27 | - reload nginx 28 | 29 | - name: Reload nginx, if necessary 30 | meta: flush_handlers 31 | 32 | - name: Check nginx redirects HTTP to HTTPS 33 | uri: 34 | url: http://localhost 35 | follow_redirects: none 36 | status_code: 301 37 | register: http_req 38 | - name: Fail if HTTP doesn't redirect to HTTPS 39 | fail: 40 | when: http_req.location != 'https://localhost/' 41 | 42 | # TODO Check that nginx is listening on port 443. Not the responsibility of this role to check for a response from the proxied service. 43 | -------------------------------------------------------------------------------- /templates/vhost.conf: -------------------------------------------------------------------------------- 1 | # based on https://wiki.jenkins-ci.org/display/JENKINS/Jenkins+behind+an+NGinX+reverse+proxy#JenkinsbehindanNGinXreverseproxy-RunningfromasubdomainwithSSL 2 | 3 | server { 4 | listen 80; 5 | server_name {{ server_names }}; 6 | return 301 https://$host$request_uri; 7 | } 8 | 9 | server { 10 | listen 443 ssl; 11 | server_name {{ server_names }}; 12 | 13 | ssl_certificate {{ ssl_certs_cert_path }}; 14 | ssl_certificate_key {{ ssl_certs_privkey_path }}; 15 | 16 | location / { 17 | proxy_set_header Host $host:$server_port; 18 | proxy_set_header X-Real-IP $remote_addr; 19 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 20 | proxy_set_header X-Forwarded-Proto $scheme; 21 | proxy_redirect http:// https://; 22 | # see the nginx_upstreams configuration 23 | proxy_pass http://http_upstream; 24 | # Required for new HTTP-based CLI 25 | proxy_http_version 1.1; 26 | proxy_request_buffering off; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = roles/:../../ 3 | -------------------------------------------------------------------------------- /tests/hosts: -------------------------------------------------------------------------------- 1 | remote ansible_connection=docker 2 | -------------------------------------------------------------------------------- /tests/requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # must match what's in `dependencies` list of `../meta/main.yml` 3 | - src: geerlingguy.nginx 4 | - src: geerlingguy.repo-epel 5 | - src: jdauphant.ssl-certs 6 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | vars: 4 | # dummy values 5 | external_hostname: localhost 6 | upstream_origin: localhost:8000 7 | roles: 8 | - gsa.https-proxy 9 | --------------------------------------------------------------------------------