Compare commits
	
		
			77 Commits
		
	
	
		
			release-0.
			...
			master
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | c98eb555f5 | |
|  | a0528ea0cb | |
|  | 441312f180 | |
|  | 2ef1e5cae4 | |
|  | e00e31d949 | |
|  | 0aab49006d | |
|  | 23d036cb69 | |
|  | c0a43f4800 | |
|  | 763f23b297 | |
|  | 5e5d5b1b86 | |
|  | 3df1b7a9ea | |
|  | d364261db8 | |
|  | b60024a970 | |
|  | ef8d313042 | |
|  | 48cdd5e593 | |
|  | a3a04facf8 | |
|  | b5eca063d5 | |
|  | 54de6b5081 | |
|  | 7c164a4887 | |
|  | a96bbe6a57 | |
|  | 8bd5c3ae21 | |
|  | 83e28636fb | |
|  | 9f01a465d8 | |
|  | 08fb44b66d | |
|  | d0e80bf79f | |
|  | 6fad4f3715 | |
|  | 8da8eef360 | |
|  | 3704dc25ff | |
|  | bbbde8d22b | |
|  | 61d8777204 | |
|  | d9a2149825 | |
|  | 86687e2887 | |
|  | 57fb98b528 | |
|  | 850f5ea5ca | |
|  | f56178b6ee | |
|  | b5c580bac9 | |
|  | 732eb15f07 | |
|  | 3776f634c0 | |
|  | 7ed1e2dfc9 | |
|  | b732f8c585 | |
|  | d234e67497 | |
|  | 1262eaf8a3 | |
|  | cdc0abff91 | |
|  | 37be5adf9c | |
|  | 8d187d9acf | |
|  | 18e0b9c29c | |
|  | 9df349f98e | |
|  | 38d220dd58 | |
|  | b5de9a539c | |
|  | b860648a33 | |
|  | 8952b217a8 | |
|  | 937def0caa | |
|  | f9e1a42329 | |
|  | d66d4a04e7 | |
|  | f94670848e | |
|  | 481b02a979 | |
|  | 9d7cfcd1cc | |
|  | bd3f672763 | |
|  | b56c9ef686 | |
|  | 64bb271b2e | |
|  | 9f7537ef34 | |
|  | f824aee3ef | |
|  | 438518509d | |
|  | 5fec096aa6 | |
|  | 3873fab889 | |
|  | 6cd01885aa | |
|  | fe3b441727 | |
|  | 40fc2ef257 | |
|  | 24bf24ffbb | |
|  | c61f0137b7 | |
|  | d56a55e21e | |
|  | 06444b36f6 | |
|  | 7f3634bba0 | |
|  | f57c05388d | |
|  | 52d5cba3e9 | |
|  | 9e55fd2c76 | |
|  | 995d0f8eaa | 
|  | @ -0,0 +1,2 @@ | ||||||
|  | .env | ||||||
|  | htmlcov | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | # Contributor Covenant Code of Conduct | ||||||
|  | 
 | ||||||
|  | ## Our Pledge | ||||||
|  | 
 | ||||||
|  | In the interest of fostering an open and welcoming environment, we as | ||||||
|  | contributors and maintainers pledge to making participation in our project and | ||||||
|  | our community a harassment-free experience for everyone, regardless of age, body | ||||||
|  | size, disability, ethnicity, sex characteristics, gender identity and expression, | ||||||
|  | level of experience, education, socio-economic status, nationality, personal | ||||||
|  | appearance, race, religion, or sexual identity and orientation. | ||||||
|  | 
 | ||||||
|  | ## Our Standards | ||||||
|  | 
 | ||||||
|  | Examples of behavior that contributes to creating a positive environment | ||||||
|  | include: | ||||||
|  | 
 | ||||||
|  | - Using welcoming and inclusive language | ||||||
|  | - Being respectful of differing viewpoints and experiences | ||||||
|  | - Gracefully accepting constructive criticism | ||||||
|  | - Focusing on what is best for the community | ||||||
|  | - Showing empathy towards other community members | ||||||
|  | 
 | ||||||
|  | Examples of unacceptable behavior by participants include: | ||||||
|  | 
 | ||||||
|  | - The use of sexualized language or imagery and unwelcome sexual attention or | ||||||
|  |   advances | ||||||
|  | - Trolling, insulting/derogatory comments, and personal or political attacks | ||||||
|  | - Public or private harassment | ||||||
|  | - Publishing others' private information, such as a physical or electronic | ||||||
|  |   address, without explicit permission | ||||||
|  | - Other conduct which could reasonably be considered inappropriate in a | ||||||
|  |   professional setting | ||||||
|  | 
 | ||||||
|  | ## Our Responsibilities | ||||||
|  | 
 | ||||||
|  | Project maintainers are responsible for clarifying the standards of acceptable | ||||||
|  | behavior and are expected to take appropriate and fair corrective action in | ||||||
|  | response to any instances of unacceptable behavior. | ||||||
|  | 
 | ||||||
|  | Project maintainers have the right and responsibility to remove, edit, or | ||||||
|  | reject comments, commits, code, wiki edits, issues, and other contributions | ||||||
|  | that are not aligned to this Code of Conduct, or to ban temporarily or | ||||||
|  | permanently any contributor for other behaviors that they deem inappropriate, | ||||||
|  | threatening, offensive, or harmful. | ||||||
|  | 
 | ||||||
|  | ## Scope | ||||||
|  | 
 | ||||||
|  | This Code of Conduct applies both within project spaces and in public spaces | ||||||
|  | when an individual is representing the project or its community. Examples of | ||||||
|  | representing a project or community include using an official project e-mail | ||||||
|  | address, posting via an official social media account, or acting as an appointed | ||||||
|  | representative at an online or offline event. Representation of a project may be | ||||||
|  | further defined and clarified by project maintainers. | ||||||
|  | 
 | ||||||
|  | ## Enforcement | ||||||
|  | 
 | ||||||
|  | Instances of abusive, harassing, or otherwise unacceptable behavior may be | ||||||
|  | reported by contacting the moderation team at nginx-oss-community@f5.com. All | ||||||
|  | complaints will be reviewed and investigated and will result in a response that | ||||||
|  | is deemed necessary and appropriate to the circumstances. The project team is | ||||||
|  | obligated to maintain confidentiality with regard to the reporter of an incident. | ||||||
|  | Further details of specific enforcement policies may be posted separately. | ||||||
|  | 
 | ||||||
|  | Project maintainers who do not follow or enforce the Code of Conduct in good | ||||||
|  | faith may face temporary or permanent repercussions as determined by other | ||||||
|  | members of the project's leadership. | ||||||
|  | 
 | ||||||
|  | ## Attribution | ||||||
|  | 
 | ||||||
|  | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, | ||||||
|  | available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html> | ||||||
|  | 
 | ||||||
|  | For answers to common questions about this code of conduct, see | ||||||
|  | <https://www.contributor-covenant.org/faq> | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | ARG PYTHON_VERSION=2 | ||||||
|  | FROM python:${PYTHON_VERSION}-alpine | ||||||
|  | 
 | ||||||
|  | COPY nginx-ldap-auth-daemon.py /usr/src/app/ | ||||||
|  | 
 | ||||||
|  | WORKDIR /usr/src/app/ | ||||||
|  | 
 | ||||||
|  | # Install required software | ||||||
|  | RUN \ | ||||||
|  |     apk --no-cache add openldap-dev && \ | ||||||
|  |     apk --no-cache add --virtual build-dependencies build-base && \ | ||||||
|  |     pip install python-ldap && \ | ||||||
|  |     apk del build-dependencies | ||||||
|  | 
 | ||||||
|  | EXPOSE 8888 | ||||||
|  | 
 | ||||||
|  | CMD ["python", "/usr/src/app/nginx-ldap-auth-daemon.py", "--host", "0.0.0.0", "--port", "8888"] | ||||||
|  | @ -0,0 +1,50 @@ | ||||||
|  | ARG PYTHON_VERSION=2 | ||||||
|  | FROM python:${PYTHON_VERSION}-alpine | ||||||
|  | 
 | ||||||
|  | WORKDIR /usr/src/app/ | ||||||
|  | COPY nginx-ldap-auth-daemon.py /usr/src/app/ | ||||||
|  | 
 | ||||||
|  | WORKDIR /tests | ||||||
|  | COPY t/ldap-auth.t /tests | ||||||
|  | COPY t/runtests.sh /tests | ||||||
|  | 
 | ||||||
|  | # Install required software | ||||||
|  | RUN \ | ||||||
|  |     apk --no-cache add openldap-dev && \ | ||||||
|  |     apk --no-cache add openldap && \ | ||||||
|  |     apk --no-cache add openldap-back-hdb && \ | ||||||
|  |     apk --no-cache add openldap-clients && \ | ||||||
|  |     apk --no-cache add openssl && \ | ||||||
|  |     apk --no-cache add nginx && \ | ||||||
|  |     apk --no-cache add nginx-mod-http-geoip && \ | ||||||
|  |     apk --no-cache add nginx-mod-stream-geoip && \ | ||||||
|  |     apk --no-cache add nginx-mod-http-image-filter && \ | ||||||
|  |     apk --no-cache add nginx-mod-stream && \ | ||||||
|  |     apk --no-cache add nginx-mod-mail && \ | ||||||
|  |     apk --no-cache add nginx-mod-http-perl && \ | ||||||
|  |     apk --no-cache add nginx-mod-http-xslt-filter && \ | ||||||
|  |     apk --no-cache add mercurial && \ | ||||||
|  |     apk --no-cache add perl && \ | ||||||
|  |     apk --no-cache add --virtual build-dependencies build-base && \ | ||||||
|  |     pip install python-ldap && \ | ||||||
|  |     pip install coverage && \ | ||||||
|  |     apk del build-dependencies | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Install tests | ||||||
|  | RUN \ | ||||||
|  |     cd /tests && \ | ||||||
|  |     hg clone https://hg.nginx.org/nginx-tests && \ | ||||||
|  |     mv ldap-auth.t nginx-tests | ||||||
|  | 
 | ||||||
|  | WORKDIR /usr/src/app/ | ||||||
|  | 
 | ||||||
|  | ENV TEST_LDAP_DAEMON=/usr/sbin/slapd | ||||||
|  | ENV TEST_LDAP_AUTH_DAEMON=/usr/src/app/nginx-ldap-auth-daemon.py | ||||||
|  | ENV TEST_NGINX_BINARY=/usr/sbin/nginx | ||||||
|  | ENV TEST_NGINX_MODULES=/usr/lib/nginx/modules | ||||||
|  | ENV LDAPTLS_REQCERT=never | ||||||
|  | 
 | ||||||
|  | WORKDIR /tests/nginx-tests | ||||||
|  | 
 | ||||||
|  | CMD ["/tests/runtests.sh"] | ||||||
							
								
								
									
										103
									
								
								README.md
								
								
								
								
							
							
						
						
									
										103
									
								
								README.md
								
								
								
								
							|  | @ -1,16 +1,18 @@ | ||||||
| # nginx-ldap-auth | # PLEASE note that this project is *not designed or hardened* for production. It is intended as a model for such connector daemons | ||||||
| 
 | 
 | ||||||
| Reference implementation of method for authenticating users on behalf of servers proxied by NGINX or NGINX Plus | ## The nginx-ldap-auth project | ||||||
| 
 | 
 | ||||||
| ## Description | This project provides a reference model implementation  of a method for authenticating users on behalf of servers proxied by NGINX or NGINX Plus. | ||||||
| 
 | 
 | ||||||
| **Note:** For ease of reading, this document refers to [NGINX Plus](http://www.nginx.com/products/), but it also applies to [open source NGINX](http://www.nginx.org/en). The prerequisite [ngx_http_auth_request_module](http://nginx.org/en/docs/http/ngx_http_auth_request_module.html) module is included both in [NGINX Plus packages](http://cs.nginx.com/repo_setup) and [prebuilt open source NGINX binaries](http://nginx.org/en/linux_packages.html). | ### Note: ### | ||||||
|  | For ease of reading, this document refers to NGINX Plus, but it also applies to open source NGINX. The prerequisite ngx_http_auth_request_module module is included both in NGINX Plus packages and prebuilt open source NGINX binaries.  | ||||||
| 
 | 
 | ||||||
| The ngx-ldap-auth software is a reference implementation of a method for authenticating users who request protected resources from servers proxied by NGINX Plus. It includes a daemon (*ldap-auth*) that communicates with an authentication server, and a sample daemon that stands in for an actual back-end server during testing, by generating an authentication cookie based on the user’s credentials. The daemons are written in Python for use with a Lightweight Directory Access Protocol (LDAP) authentication server (OpenLDAP or Microsoft Windows Active Directory 2003 and 2012). | ### Description: ### | ||||||
|  | The nginx-ldap-auth software is a reference model implementation of a method for authenticating users who request protected resources from servers proxied by NGINX Plus. It includes a daemon (ldap-auth) that communicates with an authentication server, and a sample daemon that stands in for an actual back-end server during testing, by generating an authentication cookie based on the user’s credentials. The daemons are written in Python for use with a Lightweight Directory Access Protocol (LDAP) authentication server (OpenLDAP or Microsoft Windows Active Directory 2003 and 2012). | ||||||
| 
 | 
 | ||||||
| The ldap-auth daemon, which mediates between NGINX Plus and the LDAP server, is intended to serve as a model for "connector" daemons written in other languages, for different authentication systems, or both. [NGINX, Inc. Professional Services](http://nginx.com/services/) is available to assist with such adaptations. | The ldap-auth daemon, which mediates between NGINX Plus and the LDAP server, is intended to serve as a model for "connector" daemons written in other languages, for different authentication systems, or both. NGINX, Inc. Professional Services is available to assist with such adaptations. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| For a step-by-step description of the authentication process in the reference implementation, see [How Authentication Works in the Reference Implementation](https://nginx.com/blog/nginx-plus-authenticate-users#ldap-auth-flow) in [NGINX Plus and NGINX Can Authenticate Application Users](https://nginx.com/blog/nginx-plus-authenticate-users). | For a step-by-step description of the authentication process in the reference implementation, see [How Authentication Works in the Reference Implementation](https://nginx.com/blog/nginx-plus-authenticate-users#ldap-auth-flow) in [NGINX Plus and NGINX Can Authenticate Application Users](https://nginx.com/blog/nginx-plus-authenticate-users). | ||||||
| 
 | 
 | ||||||
|  | @ -32,13 +34,25 @@ To install and configure the reference implementation, perform the following ste | ||||||
| 
 | 
 | ||||||
| 1. On the host where the ldap-auth daemon is to run, install the following additional software. We recommend using the versions that are distributed with the operating system, instead of downloading the software from an open source repository. | 1. On the host where the ldap-auth daemon is to run, install the following additional software. We recommend using the versions that are distributed with the operating system, instead of downloading the software from an open source repository. | ||||||
| 
 | 
 | ||||||
|     - Python version 2. Version 3 is not supported.  |     - Python versions 2 and 3 are supported. | ||||||
|     - The Python LDAP module, **python-ldap** (created by the [python-ldap.org](http://www.python-ldap.org) open source project). |     - The Python LDAP module, **python-ldap** (created by the [python-ldap.org](http://www.python-ldap.org) open source project). | ||||||
| 
 | 
 | ||||||
| 1. Copy the following files from your repository clone to the indicated hosts: | 1. Copy the following files from your repository clone to the indicated hosts: | ||||||
|     - **nginx-ldap-auth.conf** – NGINX Plus configuration file, which contains the minimal set of directives for testing the reference implementation. Install on the NGINX Plus host (in the **/etc/nginx/conf.d** directory if using the conventional configuration scheme). To avoid configuration conflicts, remember to move or rename any default configuration files installed with NGINX Plus. |     - **nginx-ldap-auth.conf** – NGINX Plus configuration file, which contains the minimal set of directives for testing the reference implementation. Install on the NGINX Plus host (in the **/etc/nginx/conf.d** directory if using the conventional configuration scheme). To avoid configuration conflicts, remember to move or rename any default configuration files installed with NGINX Plus. | ||||||
|     - **nginx-ldap-auth-daemon.py** – Python code for the ldap-auth daemon. Install on the host of your choice. |     - **nginx-ldap-auth-daemon.py** – Python code for the ldap-auth daemon. Install on the host of your choice. | ||||||
|  | 
 | ||||||
|  |       Alternatively, use provided Dockerfile to build Docker image: | ||||||
|  |       ``` | ||||||
|  |       docker build -t nginx-ldap-auth-daemon . | ||||||
|  |       docker run nginx-ldap-auth-daemon | ||||||
|  |       ``` | ||||||
|  |       If you desire to use a container with Python3, you can supply an appropriate build argument: | ||||||
|  |       ``` | ||||||
|  |       docker build -t nginx-ldap-auth-daemon --build-arg PYTHON_VERSION=3 . | ||||||
|  |       ``` | ||||||
|  | 
 | ||||||
|     - **nginx-ldap-auth-daemon-ctl.sh** – Sample shell script for starting and stopping the daemon. Install on the same host as the ldap-auth daemon. |     - **nginx-ldap-auth-daemon-ctl.sh** – Sample shell script for starting and stopping the daemon. Install on the same host as the ldap-auth daemon. | ||||||
|  | 
 | ||||||
|     - **backend-sample-app.py** – Python code for the daemon that during testing stands in for a real back-end application server. Install on the host of your choice. |     - **backend-sample-app.py** – Python code for the daemon that during testing stands in for a real back-end application server. Install on the host of your choice. | ||||||
| 
 | 
 | ||||||
| 1. Modify the NGINX Plus configuration file as described in [Required Modifications to the NGINX Plus Configuration File](#required-mods) below. For information about customizing your deployment, see [Customization](#customization) below. We recommend running the `nginx -t` command after making your changes to verify that the file is syntactically valid. | 1. Modify the NGINX Plus configuration file as described in [Required Modifications to the NGINX Plus Configuration File](#required-mods) below. For information about customizing your deployment, see [Customization](#customization) below. We recommend running the `nginx -t` command after making your changes to verify that the file is syntactically valid. | ||||||
|  | @ -47,21 +61,25 @@ To install and configure the reference implementation, perform the following ste | ||||||
|    <pre>root# <strong>nginx -s reload</strong></pre> |    <pre>root# <strong>nginx -s reload</strong></pre> | ||||||
| 
 | 
 | ||||||
| 1. Run the following commands to start the ldap-auth daemon and the back-end daemon. | 1. Run the following commands to start the ldap-auth daemon and the back-end daemon. | ||||||
|    <pre>root# <strong>nginx-ldap-auth-daemon-ctl.sh start</strong> |    <pre> | ||||||
|     root# <strong>python backend-sample-app.py</strong></pre> |    root# <strong>nginx-ldap-auth-daemon-ctl.sh start</strong> | ||||||
|  |    root# <strong>python backend-sample-app.py</strong> | ||||||
|  |    </pre> | ||||||
| 
 | 
 | ||||||
| 1. To test the reference implementation, use a web browser to access **http://*nginx-server-address*:8081**. Verify that the browser presents a login form. After you fill out the form and submit it, verify that the server returns the expected response to valid credentials. The sample back-end daemon returns this: | 1. To test the reference implementation, use a web browser to access **http://*nginx-server-address*:8081**. Verify that the browser presents a login form. After you fill out the form and submit it, verify that the server returns the expected response to valid credentials. The sample back-end daemon returns this: | ||||||
| <pre>Hello, world! Requested URL: <em>URL</em></pre> | <pre> | ||||||
|  | Hello, world! Requested URL: <em>URL</em> | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| <a name="required-mods"> | <a name="required-mods"></a> | ||||||
| ### Required Modifications to the NGINX Plus Configuration File | ### Required Modifications to the NGINX Plus Configuration File | ||||||
| </a> |  | ||||||
| 
 | 
 | ||||||
| Modify the **nginx-ldap-auth.conf** file, by changing values as appropriate for your deployment for the terms shown in bold font in the following configuration. | Modify the **nginx-ldap-auth.conf** file, by changing values as appropriate for your deployment for the terms shown in bold font in the following configuration. | ||||||
| 
 | 
 | ||||||
| For detailed instructions, see [Configuring the Reference Implementation](https://nginx.com/blog/nginx-plus-authenticate-users#ldap-auth-configure) in the [NGINX Plus and NGINX Can Authenticate Application Users](https://nginx.com/blog/nginx-plus-authenticate-users) blog post. The **nginx-ldap-auth.conf** file includes detailed instructions (in comments not shown here) for setting the `proxy-set-header` directives; for information about other directives, see the [NGINX reference documentation](http://nginx.org/en/docs/). | For detailed instructions, see [Configuring the Reference Implementation](https://nginx.com/blog/nginx-plus-authenticate-users#ldap-auth-configure) in the [NGINX Plus and NGINX Can Authenticate Application Users](https://nginx.com/blog/nginx-plus-authenticate-users) blog post. The **nginx-ldap-auth.conf** file includes detailed instructions (in comments not shown here) for setting the `proxy-set-header` directives; for information about other directives, see the [NGINX reference documentation](http://nginx.org/en/docs/). | ||||||
| 
 | 
 | ||||||
| <pre>http { | <pre> | ||||||
|  | http { | ||||||
|   ... |   ... | ||||||
|   proxy_cache_path <strong>cache/</strong> keys_zone=<strong>auth_cache</strong>:<strong>10m</strong>; |   proxy_cache_path <strong>cache/</strong> keys_zone=<strong>auth_cache</strong>:<strong>10m</strong>; | ||||||
| 
 | 
 | ||||||
|  | @ -74,11 +92,17 @@ For detailed instructions, see [Configuring the Reference Implementation](https: | ||||||
| 
 | 
 | ||||||
|       location = /auth-proxy { |       location = /auth-proxy { | ||||||
|          proxy_pass http://<strong>127.0.0.1</strong>:8888; |          proxy_pass http://<strong>127.0.0.1</strong>:8888; | ||||||
|  |          proxy_pass_request_body off; | ||||||
|  |          proxy_pass_request_headers off; | ||||||
|  |          proxy_set_header Content-Length ""; | ||||||
|          proxy_cache <strong>auth_cache</strong>; # Must match the name in the proxy_cache_path directive above |          proxy_cache <strong>auth_cache</strong>; # Must match the name in the proxy_cache_path directive above | ||||||
|          proxy_cache_valid 200 403 <strong>10m</strong>; |          proxy_cache_valid 200 <strong>10m</strong>; | ||||||
| 
 | 
 | ||||||
|          # URL and port for connecting to the LDAP server |          # URL and port for connecting to the LDAP server | ||||||
|          proxy_set_header X-Ldap-URL "<strong>ldaps</strong>://<strong>example.com</strong>:<strong>636</strong>"; |          proxy_set_header X-Ldap-URL "<strong>ldap</strong>://<strong>example.com</strong>"; | ||||||
|  | 
 | ||||||
|  |          # Negotiate a TLS-enabled (STARTTLS) connection before sending credentials | ||||||
|  |          proxy_set_header X-Ldap-Starttls "true"; | ||||||
| 
 | 
 | ||||||
|          # Base DN |          # Base DN | ||||||
|          proxy_set_header X-Ldap-BaseDN "<strong>cn=Users,dc=test,dc=local</strong>"; |          proxy_set_header X-Ldap-BaseDN "<strong>cn=Users,dc=test,dc=local</strong>"; | ||||||
|  | @ -90,25 +114,38 @@ For detailed instructions, see [Configuring the Reference Implementation](https: | ||||||
|          proxy_set_header X-Ldap-BindPass "<strong>secret</strong>"; |          proxy_set_header X-Ldap-BindPass "<strong>secret</strong>"; | ||||||
|       } |       } | ||||||
|    } |    } | ||||||
| }</pre> | } | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| If the authentication server runs Active Directory rather than OpenLDAP, uncomment the following directive as shown: | If the authentication server runs Active Directory rather than OpenLDAP, uncomment the following directive as shown: | ||||||
| 
 |  | ||||||
| ``` | ``` | ||||||
| proxy_set_header X-Ldap-Template "(SAMAccountName=%(username)s)"; | proxy_set_header X-Ldap-Template "(sAMAccountName=%(username)s)"; | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| The reference implementation uses cookie-based authentication. If you are using HTTP basic authentication instead, comment out the following directives as shown: | In addition, the **X-Ldap-Template** header can be used to create complex LDAP searches. The code in ldap-auth-daemon creates a search filter that is based on this template header. By default, template is empty, and does not make any effect on LDAP search. However, you may decide for instance to authenticate only users from a specific user group (see LDAP documentation for more information regarding filters). | ||||||
| 
 | 
 | ||||||
| <pre><strong>#</strong>proxy_set_header X-CookieName "nginxauth"; | Suppose, your web resource should only be available for users from `group1` group. | ||||||
| <strong>#</strong>proxy_set_header Cookie nginxauth=$cookie_nginxauth;</pre> | In such a case you can define `X-Ldap-Template` template as follows: | ||||||
|  | ``` | ||||||
|  | proxy_set_header X-Ldap-Template "(&(cn=%(username)s)(memberOf=cn=group1,cn=Users,dc=example,dc=com))"; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The search filters can be combined from less complex filters using boolean operations and can be rather complex. | ||||||
|  | 
 | ||||||
|  | The reference implementation uses cookie-based authentication. If you are using HTTP basic authentication instead, comment out the following directives, and enable the Authorization header as shown: | ||||||
|  | 
 | ||||||
|  | <pre> | ||||||
|  | <strong>#</strong>proxy_set_header X-CookieName "nginxauth"; | ||||||
|  | <strong>#</strong>proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  | <strong>proxy_set_header Authorization $http_authorization;</strong> | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| ## Customization | ## Customization | ||||||
| ### Caching | ### Caching | ||||||
| 
 | 
 | ||||||
| The **nginx-ldap-auth.conf** file enables caching of both data and credentials. To disable caching, comment out the four `proxy_cache*` directives as shown: | The **nginx-ldap-auth.conf** file enables caching of both data and credentials. To disable caching, comment out the four `proxy_cache*` directives as shown: | ||||||
| 
 | <pre> | ||||||
| <pre>http { | http { | ||||||
|   ... |   ... | ||||||
|   <strong>#</strong>proxy_cache_path cache/ keys_zone=auth_cache:10m; |   <strong>#</strong>proxy_cache_path cache/ keys_zone=auth_cache:10m; | ||||||
|   ... |   ... | ||||||
|  | @ -118,29 +155,31 @@ The **nginx-ldap-auth.conf** file enables caching of both data and credentials. | ||||||
|       <strong>#</strong>proxy_cache auth_cache; |       <strong>#</strong>proxy_cache auth_cache; | ||||||
|       # note that cookie is added to cache key |       # note that cookie is added to cache key | ||||||
|       <strong>#</strong>proxy_cache_key "$http_authorization$cookie_nginxauth"; |       <strong>#</strong>proxy_cache_key "$http_authorization$cookie_nginxauth"; | ||||||
|       <strong>#</strong>proxy_cache_valid 200 403 10m; |       <strong>#</strong>proxy_cache_valid 200 10m; | ||||||
|      } |      } | ||||||
|    } |    } | ||||||
| }</pre> | } | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| ### Optional LDAP Parameters | ### Optional LDAP Parameters | ||||||
| 
 | 
 | ||||||
| If you want to change the value for the `template` parameter that the ldap-auth daemon passes to the OpenLDAP server by default, uncomment the following directive as shown, and change the value: | If you want to change the value for the `template` parameter that the ldap-auth daemon passes to the OpenLDAP server by default, uncomment the following directive as shown, and change the value: | ||||||
| 
 | <pre> | ||||||
| <pre>proxy_set_header X-Ldap-Template "<strong>(cn=%(username)s)</strong>";</pre> | proxy_set_header X-Ldap-Template "<strong>(cn=%(username)s)</strong>"; | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| If you want to change the realm name from the default value (**Restricted**), uncomment and change the following directive: | If you want to change the realm name from the default value (**Restricted**), uncomment and change the following directive: | ||||||
| 
 | <pre> | ||||||
| <pre>proxy_set_header X-Ldap-Realm "<strong>Restricted</strong>";</pre> | proxy_set_header X-Ldap-Realm "<strong>Restricted</strong>"; | ||||||
|  | </pre> | ||||||
| 
 | 
 | ||||||
| ### Authentication Server | ### Authentication Server | ||||||
| 
 | 
 | ||||||
| To modify the ldap-auth daemon to communicate with a different (non-LDAP) type of authentication server, write a new authentication-handler class to replace `LDAPAuthHandler` in the **ngx-ldap-auth-daemon.py** script. | To modify the ldap-auth daemon to communicate with a different (non-LDAP) type of authentication server, write a new authentication-handler class to replace `LDAPAuthHandler` in the **nginx-ldap-auth-daemon.py** script. | ||||||
| 
 | 
 | ||||||
| ## Compatibility | ## Compatibility | ||||||
| 
 | 
 | ||||||
| The auth daemon was tested against default configurations of the following LDAP servers: | The auth daemon was tested against default configurations of the following LDAP servers: | ||||||
| 
 |  | ||||||
| * [OpenLDAP](http://www.openldap.org/)</li> | * [OpenLDAP](http://www.openldap.org/)</li> | ||||||
| * Microsoft Windows Server Active Directory 2003</li> | * Microsoft Windows Server Active Directory 2003</li> | ||||||
| * Microsoft Windows Server Active Directory 2012</li> | * Microsoft Windows Server Active Directory 2012</li> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| #!/bin/sh | #!/bin/sh | ||||||
| ''''which python2 >/dev/null && exec python2 "$0" "$@" # ''' |  | ||||||
| ''''which python  >/dev/null && exec python  "$0" "$@" # ''' | ''''which python  >/dev/null && exec python  "$0" "$@" # ''' | ||||||
| 
 | 
 | ||||||
| # Copyright (C) 2014-2015 Nginx, Inc. | # Copyright (C) 2014-2015 Nginx, Inc. | ||||||
|  | @ -9,13 +8,29 @@ | ||||||
| # 1) accepts GET  requests on /login and responds with a login form | # 1) accepts GET  requests on /login and responds with a login form | ||||||
| # 2) accepts POST requests on /login, sets a cookie, and responds with redirect | # 2) accepts POST requests on /login, sets a cookie, and responds with redirect | ||||||
| 
 | 
 | ||||||
| import sys, os, signal, base64, Cookie, cgi, urlparse | import sys, os, signal, base64, cgi | ||||||
|  | if sys.version_info.major == 2: | ||||||
|  |     from urlparse import urlparse | ||||||
|  |     from Cookie import BaseCookie | ||||||
|     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler |     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler | ||||||
|  | elif sys.version_info.major == 3: | ||||||
|  |     from urllib.parse import urlparse | ||||||
|  |     from http.cookies import BaseCookie | ||||||
|  |     from http.server import HTTPServer, BaseHTTPRequestHandler | ||||||
| 
 | 
 | ||||||
| Listen = ('localhost', 9000) | Listen = ('localhost', 9000) | ||||||
| 
 | 
 | ||||||
| import threading | import threading | ||||||
|  | if sys.version_info.major == 2: | ||||||
|     from SocketServer import ThreadingMixIn |     from SocketServer import ThreadingMixIn | ||||||
|  | elif sys.version_info.major == 3: | ||||||
|  |     from socketserver import ThreadingMixIn | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def ensure_bytes(data): | ||||||
|  |     return data if sys.version_info.major == 2 else data.encode("utf-8") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class AuthHTTPServer(ThreadingMixIn, HTTPServer): | class AuthHTTPServer(ThreadingMixIn, HTTPServer): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
|  | @ -23,14 +38,14 @@ class AppHandler(BaseHTTPRequestHandler): | ||||||
| 
 | 
 | ||||||
|     def do_GET(self): |     def do_GET(self): | ||||||
| 
 | 
 | ||||||
|         url = urlparse.urlparse(self.path) |         url = urlparse(self.path) | ||||||
| 
 | 
 | ||||||
|         if url.path.startswith("/login"): |         if url.path.startswith("/login"): | ||||||
|             return self.auth_form() |             return self.auth_form() | ||||||
| 
 | 
 | ||||||
|         self.send_response(200) |         self.send_response(200) | ||||||
|         self.end_headers() |         self.end_headers() | ||||||
|         self.wfile.write('Hello, world! Requested URL: ' + self.path + '\n') |         self.wfile.write(ensure_bytes('Hello, world! Requested URL: ' + self.path + '\n')) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     # send login form html |     # send login form html | ||||||
|  | @ -59,7 +74,7 @@ class AppHandler(BaseHTTPRequestHandler): | ||||||
|         <tr> |         <tr> | ||||||
|           <td>Username: <input type="text" name="username"/></td> |           <td>Username: <input type="text" name="username"/></td> | ||||||
|         <tr> |         <tr> | ||||||
|           <td>Password: <input type="text" name="password"/></td> |           <td>Password: <input type="password" name="password"/></td> | ||||||
|         <tr> |         <tr> | ||||||
|           <td><input type="submit" value="Login"></td> |           <td><input type="submit" value="Login"></td> | ||||||
|       </table> |       </table> | ||||||
|  | @ -70,7 +85,7 @@ class AppHandler(BaseHTTPRequestHandler): | ||||||
| 
 | 
 | ||||||
|         self.send_response(200) |         self.send_response(200) | ||||||
|         self.end_headers() |         self.end_headers() | ||||||
|         self.wfile.write(html.replace('TARGET', target)) |         self.wfile.write(ensure_bytes(html.replace('TARGET', target))) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     # processes posted form and sets the cookie with login/password |     # processes posted form and sets the cookie with login/password | ||||||
|  | @ -103,8 +118,10 @@ class AppHandler(BaseHTTPRequestHandler): | ||||||
|             # and share a key with auth daemon that extracts this information |             # and share a key with auth daemon that extracts this information | ||||||
|             # |             # | ||||||
|             # WARNING WARNING WARNING |             # WARNING WARNING WARNING | ||||||
|             enc = base64.b64encode(user + ':' + passwd) |             enc = base64.b64encode(ensure_bytes(user + ':' + passwd)) | ||||||
|             self.send_header('Set-Cookie', 'nginxauth=' + enc + '; httponly') |             if sys.version_info.major == 3: | ||||||
|  |                 enc = enc.decode() | ||||||
|  |             self.send_header('Set-Cookie', b'nginxauth=' + enc + b'; httponly') | ||||||
| 
 | 
 | ||||||
|             self.send_header('Location', target) |             self.send_header('Location', target) | ||||||
|             self.end_headers() |             self.end_headers() | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | nginx-ldap-auth (0.0.3-1) UNRELEASED; urgency=low | ||||||
|  | 
 | ||||||
|  |   * Initial release | ||||||
|  | 
 | ||||||
|  |  -- Ippolitov Igor <iippolitov@nginx.com>  Wed, 02 Nov 2016 14:32:15 +0300 | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 9 | ||||||
|  | @ -0,0 +1,14 @@ | ||||||
|  | Source: nginx-ldap-auth | ||||||
|  | Maintainer: Ippolitov Igor <iippolitov@nginx.com> | ||||||
|  | Section: misc | ||||||
|  | Priority: optional | ||||||
|  | Standards-Version: 3.9.7 | ||||||
|  | Build-Depends: debhelper (>= 9), dh-systemd, python, dh-python, dh-exec | ||||||
|  | 
 | ||||||
|  | Package: nginx-ldap-auth | ||||||
|  | Architecture: all | ||||||
|  | Depends:  systemd, python(>=2.6), python-ldap, python-argparse | ||||||
|  | Description: a reference implementation of an authentication helper for Nginx | ||||||
|  |  This is a reference implementation of an authentication helper for Nginx. | ||||||
|  |  It listens for incoming requests and uses parameters from headers | ||||||
|  |  to bind to a remote LDAP directory and try authenticating a person. | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | #! /bin/sh | ||||||
|  | ### BEGIN INIT INFO | ||||||
|  | # Provides:		nginx-ldap-auth | ||||||
|  | # Required-Start:	$syslog $remote_fs | ||||||
|  | # Required-Stop:	$syslog $remote_fs | ||||||
|  | # Should-Start:		$local_fs | ||||||
|  | # Should-Stop:		$local_fs | ||||||
|  | # Default-Start:	2 3 4 5 | ||||||
|  | # Default-Stop:		0 1 6 | ||||||
|  | # Short-Description:	nginx-ldap-auth - nginx helper for LDAP authentication | ||||||
|  | # Description:		nginx-ldap-auth - nginx helper for LDAP authentication | ||||||
|  | ### END INIT INFO | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | DAEMON=/usr/bin/nginx-ldap-auth-daemon | ||||||
|  | NAME=nginx-ldap-auth | ||||||
|  | 
 | ||||||
|  | test -x $DAEMON || exit 0 | ||||||
|  | 
 | ||||||
|  | if [ -r /etc/default/$NAME ] | ||||||
|  | then | ||||||
|  | 	. /etc/default/$NAME | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | . /lib/lsb/init-functions | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | case "$1" in | ||||||
|  |   start) | ||||||
|  | 	echo -n "Starting $DESC: " | ||||||
|  | 	mkdir -p $RUNDIR | ||||||
|  | 	touch $PIDFILE | ||||||
|  | 	chown $USER:$GROUP $RUNDIR $PIDFILE | ||||||
|  | 	chmod 755 $RUNDIR | ||||||
|  | 
 | ||||||
|  | 	if [ -n "$ULIMIT" ] | ||||||
|  | 	then | ||||||
|  | 		ulimit -n $ULIMIT | ||||||
|  | 	fi | ||||||
|  | 
 | ||||||
|  |     SSDOPTS="--quiet --oknodo --background --no-close --make-pidfile --pidfile $PIDFILE --chuid $USER:$GROUP --exec $DAEMON" | ||||||
|  |     DAEMON_ARGS="$URL $BASE $BIND_DN $BIND_PASS $COOKIE $FILTER $REALM" | ||||||
|  | 
 | ||||||
|  | 	if start-stop-daemon --start $SSDOPTS -- $DAEMON_ARGS > $LOG 2>&1 | ||||||
|  | 	then | ||||||
|  | 		echo "$NAME." | ||||||
|  | 	else | ||||||
|  | 		echo "failed" | ||||||
|  | 	fi | ||||||
|  | 	;; | ||||||
|  |   stop) | ||||||
|  | 	echo -n "Stopping $DESC: " | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	if start-stop-daemon --stop --retry forever/TERM/1 --quiet --oknodo --remove-pidfile --pidfile $PIDFILE --exec $DAEMON | ||||||
|  | 	then | ||||||
|  | 		echo "$NAME." | ||||||
|  | 	else | ||||||
|  | 		echo "failed" | ||||||
|  | 	fi | ||||||
|  | 	sleep 1 | ||||||
|  | 	;; | ||||||
|  | 
 | ||||||
|  |   restart|force-reload) | ||||||
|  | 	${0} stop | ||||||
|  | 	${0} start | ||||||
|  | 	;; | ||||||
|  | 
 | ||||||
|  |   status) | ||||||
|  | 	status_of_proc -p ${PIDFILE} ${DAEMON} ${NAME} | ||||||
|  | 	;; | ||||||
|  | 
 | ||||||
|  |   *) | ||||||
|  | 	echo "Usage: /etc/init.d/$NAME {start|stop|restart|force-reload|status}" >&2 | ||||||
|  | 	exit 1 | ||||||
|  | 	;; | ||||||
|  | esac | ||||||
|  | 
 | ||||||
|  | exit 0 | ||||||
|  | @ -0,0 +1,4 @@ | ||||||
|  | #!/usr/bin/dh-exec | ||||||
|  | nginx-ldap-auth-daemon.py => usr/bin/nginx-ldap-auth-daemon | ||||||
|  | nginx-ldap-auth.default => etc/default/nginx-ldap-auth | ||||||
|  | nginx-ldap-auth.service => lib/systemd/system/nginx-ldap-auth.service | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | /var/log/nginx-ldap-auth/daemon.log { | ||||||
|  |         daily | ||||||
|  |         missingok | ||||||
|  |         rotate 7 | ||||||
|  |         compress | ||||||
|  |         notifempty | ||||||
|  |         copytruncate | ||||||
|  | } | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | set -e | ||||||
|  | 
 | ||||||
|  | getent group nginx-ldap-auth > /dev/null || groupadd -r nginx-ldap-auth | ||||||
|  | getent passwd nginx-ldap-auth > /dev/null || \ | ||||||
|  |     useradd -r -d /var/run -g nginx-ldap-auth \ | ||||||
|  |     -s /sbin/nologin -c "Nginx auth helper" nginx-ldap-auth | ||||||
|  | 
 | ||||||
|  | install -d -m755 -o nginx-ldap-auth -g nginx-ldap-auth /var/log/nginx-ldap-auth | ||||||
|  | 
 | ||||||
|  | #DEBHELPER# | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | #!/usr/bin/make -f | ||||||
|  | %: | ||||||
|  | 	dh $@ --with python2 --with systemd | ||||||
|  | @ -0,0 +1,42 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | CMD=nginx-ldap-auth-daemon.py | ||||||
|  | if [ ! -f "$CMD" ]; then | ||||||
|  |     echo "Please run '$0' from the same directory where '$CMD' file resides" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | CMD=$PWD/$CMD | ||||||
|  | PIDFILE=./nginx-ldap-auth-daemon.pid | ||||||
|  | 
 | ||||||
|  | . /etc/init.d/functions | ||||||
|  | 
 | ||||||
|  | start() { | ||||||
|  |     echo -n "Starting ldap-auth-daemon: " | ||||||
|  |     if [ -s ${PIDFILE} ]; then | ||||||
|  |        RETVAL=1 | ||||||
|  |        echo -n "Already running !" && warning | ||||||
|  |        echo | ||||||
|  |     else | ||||||
|  |        nohup ${CMD} >/dev/null 2>&1 & | ||||||
|  |        RETVAL=$? | ||||||
|  |        PID=$! | ||||||
|  |        [ $RETVAL -eq 0 ] && success || failure | ||||||
|  |        echo | ||||||
|  |        echo $PID > ${PIDFILE} | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | case $1 in | ||||||
|  |     "start") | ||||||
|  |         start | ||||||
|  |     ;; | ||||||
|  |     "stop") | ||||||
|  |         echo -n "Stopping ldap-auth-daemon: " | ||||||
|  |         killproc -p $PIDFILE $CMD | ||||||
|  |         echo | ||||||
|  |     ;; | ||||||
|  |     *) | ||||||
|  |         echo "Usage: $0 <start|stop>" | ||||||
|  |     ;; | ||||||
|  | esac | ||||||
|  | @ -1,13 +1,27 @@ | ||||||
| #!/bin/sh | #!/bin/sh | ||||||
| ''''which python2 >/dev/null && exec python2 "$0" "$@" # ''' | ''''[ -z $LOG ] && export LOG=/dev/stdout # ''' | ||||||
| ''''which python  >/dev/null && exec python  "$0" "$@" # ''' | ''''which python  >/dev/null && exec python  -u "$0" "$@" >> $LOG 2>&1 # ''' | ||||||
| 
 | 
 | ||||||
| # Copyright (C) 2014-2015 Nginx, Inc. | # Copyright (C) 2014-2022 Nginx, Inc. | ||||||
| 
 | 
 | ||||||
| import sys, os, signal, base64, ldap, Cookie | import sys | ||||||
|  | import os | ||||||
|  | import signal | ||||||
|  | import base64 | ||||||
|  | import ldap | ||||||
|  | from ldap.filter import escape_filter_chars | ||||||
|  | import argparse | ||||||
|  | 
 | ||||||
|  | if sys.version_info.major == 2: | ||||||
|  |     from Cookie import BaseCookie | ||||||
|     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler |     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler | ||||||
|  | elif sys.version_info.major == 3: | ||||||
|  |     from http.cookies import BaseCookie | ||||||
|  |     from http.server import HTTPServer, BaseHTTPRequestHandler | ||||||
| 
 | 
 | ||||||
| Listen = ('localhost', 8888) | if not hasattr(__builtins__, "basestring"): basestring = (str, bytes) | ||||||
|  | 
 | ||||||
|  | #Listen = ('localhost', 8888) | ||||||
| #Listen = "/tmp/auth.sock"    # Also uncomment lines in 'Requests are | #Listen = "/tmp/auth.sock"    # Also uncomment lines in 'Requests are | ||||||
|                               # processed with UNIX sockets' section below |                               # processed with UNIX sockets' section below | ||||||
| 
 | 
 | ||||||
|  | @ -16,7 +30,12 @@ Listen = ('localhost', 8888) | ||||||
| # ----------------------------------------------------------------------------- | # ----------------------------------------------------------------------------- | ||||||
| # Requests are processed in separate thread | # Requests are processed in separate thread | ||||||
| import threading | import threading | ||||||
|  | 
 | ||||||
|  | if sys.version_info.major == 2: | ||||||
|     from SocketServer import ThreadingMixIn |     from SocketServer import ThreadingMixIn | ||||||
|  | elif sys.version_info.major == 3: | ||||||
|  |     from socketserver import ThreadingMixIn | ||||||
|  | 
 | ||||||
| class AuthHTTPServer(ThreadingMixIn, HTTPServer): | class AuthHTTPServer(ThreadingMixIn, HTTPServer): | ||||||
|     pass |     pass | ||||||
| # ----------------------------------------------------------------------------- | # ----------------------------------------------------------------------------- | ||||||
|  | @ -61,7 +80,7 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
|         if auth_header is None or not auth_header.lower().startswith('basic '): |         if auth_header is None or not auth_header.lower().startswith('basic '): | ||||||
| 
 | 
 | ||||||
|             self.send_response(401) |             self.send_response(401) | ||||||
|             self.send_header('WWW-Authenticate', 'Basic realm=' + ctx['realm']) |             self.send_header('WWW-Authenticate', 'Basic realm="' + ctx['realm'] + '"') | ||||||
|             self.send_header('Cache-Control', 'no-cache') |             self.send_header('Cache-Control', 'no-cache') | ||||||
|             self.end_headers() |             self.end_headers() | ||||||
| 
 | 
 | ||||||
|  | @ -71,14 +90,15 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             auth_decoded = base64.b64decode(auth_header[6:]) |             auth_decoded = base64.b64decode(auth_header[6:]) | ||||||
|             user, passwd = auth_decoded.split(':', 2) |             if sys.version_info.major == 3: auth_decoded = auth_decoded.decode("utf-8") | ||||||
|  |             user, passwd = auth_decoded.split(':', 1) | ||||||
| 
 | 
 | ||||||
|         except: |         except: | ||||||
|             self.auth_failed(ctx) |             self.auth_failed(ctx) | ||||||
|             return True |             return True | ||||||
|         |         | ||||||
|         ctx['user'] = user |  | ||||||
|         ctx['pass'] = passwd |         ctx['pass'] = passwd | ||||||
|  |         ctx['user'] = ldap.filter.escape_filter_chars(user) | ||||||
| 
 | 
 | ||||||
|         # Continue request processing |         # Continue request processing | ||||||
|         return False |         return False | ||||||
|  | @ -86,7 +106,7 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
|     def get_cookie(self, name): |     def get_cookie(self, name): | ||||||
|         cookies = self.headers.get('Cookie') |         cookies = self.headers.get('Cookie') | ||||||
|         if cookies: |         if cookies: | ||||||
|             authcookie = Cookie.BaseCookie(cookies).get(name) |             authcookie = BaseCookie(cookies).get(name) | ||||||
|             if authcookie: |             if authcookie: | ||||||
|                 return authcookie.value |                 return authcookie.value | ||||||
|             else: |             else: | ||||||
|  | @ -114,7 +134,9 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
|             msg += ', login="%s"' % ctx['user'] |             msg += ', login="%s"' % ctx['user'] | ||||||
| 
 | 
 | ||||||
|         self.log_error(msg) |         self.log_error(msg) | ||||||
|         self.send_response(403) |         self.send_response(401) | ||||||
|  |         self.send_header('WWW-Authenticate', 'Basic realm="' + ctx['realm'] + '"') | ||||||
|  |         self.send_header('Cache-Control', 'no-cache') | ||||||
|         self.end_headers() |         self.end_headers() | ||||||
| 
 | 
 | ||||||
|     def get_params(self): |     def get_params(self): | ||||||
|  | @ -126,7 +148,12 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
|         else: |         else: | ||||||
|             addr = "-" |             addr = "-" | ||||||
| 
 | 
 | ||||||
|         sys.stdout.write("%s - %s [%s] %s\n" % (addr, self.ctx['user'], |         if not hasattr(self, 'ctx'): | ||||||
|  |             user = '-' | ||||||
|  |         else: | ||||||
|  |             user = self.ctx['user'] | ||||||
|  | 
 | ||||||
|  |         sys.stdout.write("%s - %s [%s] %s\n" % (addr, user, | ||||||
|                          self.log_date_time_string(), format % args)) |                          self.log_date_time_string(), format % args)) | ||||||
| 
 | 
 | ||||||
|     def log_error(self, format, *args): |     def log_error(self, format, *args): | ||||||
|  | @ -135,20 +162,27 @@ class AuthHandler(BaseHTTPRequestHandler): | ||||||
| 
 | 
 | ||||||
| # Verify username/password against LDAP server | # Verify username/password against LDAP server | ||||||
| class LDAPAuthHandler(AuthHandler): | class LDAPAuthHandler(AuthHandler): | ||||||
| 
 |  | ||||||
|     # Parameters to put into self.ctx from the HTTP header of auth request |     # Parameters to put into self.ctx from the HTTP header of auth request | ||||||
|     def get_params(self): |     params =  { | ||||||
|         return { |  | ||||||
|              # parameter      header         default |              # parameter      header         default | ||||||
|              'realm': ('X-Ldap-Realm', 'Restricted'), |              'realm': ('X-Ldap-Realm', 'Restricted'), | ||||||
|              'url': ('X-Ldap-URL', None), |              'url': ('X-Ldap-URL', None), | ||||||
|  |              'starttls': ('X-Ldap-Starttls', 'false'), | ||||||
|  |              'disable_referrals': ('X-Ldap-DisableReferrals', 'false'), | ||||||
|              'basedn': ('X-Ldap-BaseDN', None), |              'basedn': ('X-Ldap-BaseDN', None), | ||||||
|              'template': ('X-Ldap-Template', '(cn=%(username)s)'), |              'template': ('X-Ldap-Template', '(cn=%(username)s)'), | ||||||
|              'binddn': ('X-Ldap-BindDN', 'cn=anonymous'), |              'binddn': ('X-Ldap-BindDN', ''), | ||||||
|              'bindpasswd': ('X-Ldap-BindPass', ''), |              'bindpasswd': ('X-Ldap-BindPass', ''), | ||||||
|              'cookiename': ('X-CookieName', '') |              'cookiename': ('X-CookieName', '') | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def set_params(cls, params): | ||||||
|  |         cls.params = params | ||||||
|  | 
 | ||||||
|  |     def get_params(self): | ||||||
|  |         return self.params | ||||||
|  | 
 | ||||||
|     # GET handler for the authentication request |     # GET handler for the authentication request | ||||||
|     def do_GET(self): |     def do_GET(self): | ||||||
| 
 | 
 | ||||||
|  | @ -168,12 +202,35 @@ class LDAPAuthHandler(AuthHandler): | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|  |             # check that uri and baseDn are set | ||||||
|  |             # either from cli or a request | ||||||
|  |             if not ctx['url']: | ||||||
|  |                 self.log_message('LDAP URL is not set!') | ||||||
|  |                 return | ||||||
|  |             if not ctx['basedn']: | ||||||
|  |                 self.log_message('LDAP baseDN is not set!') | ||||||
|  |                 return | ||||||
|  | 
 | ||||||
|             ctx['action'] = 'initializing LDAP connection' |             ctx['action'] = 'initializing LDAP connection' | ||||||
|             ldap_obj = ldap.initialize(ctx['url']); |             ldap_obj = ldap.initialize(ctx['url']); | ||||||
| 
 | 
 | ||||||
|             # See http://www.python-ldap.org/faq.shtml |             # Python-ldap module documentation advises to always | ||||||
|             # uncomment, if required |             # explicitely set the LDAP version to use after running | ||||||
|             # ldap_obj.set_option(ldap.OPT_REFERRALS, 0) |             # initialize() and recommends using LDAPv3. (LDAPv2 is | ||||||
|  |             # deprecated since 2003 as per RFC3494) | ||||||
|  |             # | ||||||
|  |             # Also, the STARTTLS extension requires the | ||||||
|  |             # use of LDAPv3 (RFC2830). | ||||||
|  |             ldap_obj.protocol_version=ldap.VERSION3 | ||||||
|  | 
 | ||||||
|  |             # Establish a STARTTLS connection if required by the | ||||||
|  |             # headers. | ||||||
|  |             if ctx['starttls'] == 'true': | ||||||
|  |                 ldap_obj.start_tls_s() | ||||||
|  | 
 | ||||||
|  |             # See https://www.python-ldap.org/en/latest/faq.html | ||||||
|  |             if ctx['disable_referrals'] == 'true': | ||||||
|  |                 ldap_obj.set_option(ldap.OPT_REFERRALS, 0) | ||||||
| 
 | 
 | ||||||
|             ctx['action'] = 'binding as search user' |             ctx['action'] = 'binding as search user' | ||||||
|             ldap_obj.bind_s(ctx['binddn'], ctx['bindpasswd'], ldap.AUTH_SIMPLE) |             ldap_obj.bind_s(ctx['binddn'], ctx['bindpasswd'], ldap.AUTH_SIMPLE) | ||||||
|  | @ -190,13 +247,27 @@ class LDAPAuthHandler(AuthHandler): | ||||||
|                                           searchfilter, ['objectclass'], 1) |                                           searchfilter, ['objectclass'], 1) | ||||||
| 
 | 
 | ||||||
|             ctx['action'] = 'verifying search query results' |             ctx['action'] = 'verifying search query results' | ||||||
|             if len(results) < 1: | 
 | ||||||
|  |             nres = len(results) | ||||||
|  | 
 | ||||||
|  |             if nres < 1: | ||||||
|                 self.auth_failed(ctx, 'no objects found') |                 self.auth_failed(ctx, 'no objects found') | ||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
|             ctx['action'] = 'binding as an existing user' |             if nres > 1: | ||||||
|             ldap_dn = results[0][0] |                 self.log_message("note: filter match multiple objects: %d, using first" % nres) | ||||||
|             ctx['action'] += ' "%s"' % ldap_dn | 
 | ||||||
|  |             user_entry = results[0] | ||||||
|  |             ldap_dn = user_entry[0] | ||||||
|  | 
 | ||||||
|  |             if ldap_dn == None: | ||||||
|  |                 self.auth_failed(ctx, 'matched object has no dn') | ||||||
|  |                 return | ||||||
|  | 
 | ||||||
|  |             self.log_message('attempting to bind using dn "%s"' % (ldap_dn)) | ||||||
|  | 
 | ||||||
|  |             ctx['action'] = 'binding as an existing user "%s"' % ldap_dn | ||||||
|  | 
 | ||||||
|             ldap_obj.bind_s(ldap_dn, ctx['pass'], ldap.AUTH_SIMPLE) |             ldap_obj.bind_s(ldap_dn, ctx['pass'], ldap.AUTH_SIMPLE) | ||||||
| 
 | 
 | ||||||
|             self.log_message('Auth OK for user "%s"' % (ctx['user'])) |             self.log_message('Auth OK for user "%s"' % (ctx['user'])) | ||||||
|  | @ -218,9 +289,64 @@ def exit_handler(signal, frame): | ||||||
|             ex, value, trace = sys.exc_info() |             ex, value, trace = sys.exc_info() | ||||||
|             sys.stderr.write('Failed to remove socket "%s": %s\n' % |             sys.stderr.write('Failed to remove socket "%s": %s\n' % | ||||||
|                              (Listen, str(value))) |                              (Listen, str(value))) | ||||||
|  |             sys.stderr.flush() | ||||||
|     sys.exit(0) |     sys.exit(0) | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  |     parser = argparse.ArgumentParser( | ||||||
|  |         description="""Simple Nginx LDAP authentication helper.""") | ||||||
|  |     # Group for listen options: | ||||||
|  |     group = parser.add_argument_group("Listen options") | ||||||
|  |     group.add_argument('--host',  metavar="hostname", | ||||||
|  |         default="localhost", help="host to bind (Default: localhost)") | ||||||
|  |     group.add_argument('-p', '--port', metavar="port", type=int, | ||||||
|  |         default=8888, help="port to bind (Default: 8888)") | ||||||
|  |     # ldap options: | ||||||
|  |     group = parser.add_argument_group(title="LDAP options") | ||||||
|  |     group.add_argument('-u', '--url', metavar="URL", | ||||||
|  |         default="ldap://localhost:389", | ||||||
|  |         help=("LDAP URI to query (Default: ldap://localhost:389)")) | ||||||
|  |     group.add_argument('-s', '--starttls', metavar="starttls", | ||||||
|  |         default="false", | ||||||
|  |         help=("Establish a STARTTLS protected session (Default: false)")) | ||||||
|  |     group.add_argument('--disable-referrals', metavar="disable_referrals", | ||||||
|  |         default="false", | ||||||
|  |         help=("Sets ldap.OPT_REFERRALS to zero (Default: false)")) | ||||||
|  |     group.add_argument('-b', metavar="baseDn", dest="basedn", default='', | ||||||
|  |         help="LDAP base dn (Default: unset)") | ||||||
|  |     group.add_argument('-D', metavar="bindDn", dest="binddn", default='', | ||||||
|  |         help="LDAP bind DN (Default: anonymous)") | ||||||
|  |     group.add_argument('-w', metavar="passwd", dest="bindpw", default='', | ||||||
|  |         help="LDAP password for the bind DN (Default: unset)") | ||||||
|  |     group.add_argument('-f', '--filter', metavar='filter', | ||||||
|  |         default='(cn=%(username)s)', | ||||||
|  |         help="LDAP filter (Default: cn=%%(username)s)") | ||||||
|  |     # http options: | ||||||
|  |     group = parser.add_argument_group(title="HTTP options") | ||||||
|  |     group.add_argument('-R', '--realm', metavar='"Restricted Area"', | ||||||
|  |         default="Restricted", help='HTTP auth realm (Default: "Restricted")') | ||||||
|  |     group.add_argument('-c', '--cookie', metavar="cookiename", | ||||||
|  |         default="", help="HTTP cookie name to set in (Default: unset)") | ||||||
|  | 
 | ||||||
|  |     args = parser.parse_args() | ||||||
|  |     global Listen | ||||||
|  |     Listen = (args.host, args.port) | ||||||
|  |     auth_params = { | ||||||
|  |              'realm': ('X-Ldap-Realm', args.realm), | ||||||
|  |              'url': ('X-Ldap-URL', args.url), | ||||||
|  |              'starttls': ('X-Ldap-Starttls', args.starttls), | ||||||
|  |              'disable_referrals': ('X-Ldap-DisableReferrals', args.disable_referrals), | ||||||
|  |              'basedn': ('X-Ldap-BaseDN', args.basedn), | ||||||
|  |              'template': ('X-Ldap-Template', args.filter), | ||||||
|  |              'binddn': ('X-Ldap-BindDN', args.binddn), | ||||||
|  |              'bindpasswd': ('X-Ldap-BindPass', args.bindpw), | ||||||
|  |              'cookiename': ('X-CookieName', args.cookie) | ||||||
|  |     } | ||||||
|  |     LDAPAuthHandler.set_params(auth_params) | ||||||
|     server = AuthHTTPServer(Listen, LDAPAuthHandler) |     server = AuthHTTPServer(Listen, LDAPAuthHandler) | ||||||
|     signal.signal(signal.SIGINT, exit_handler) |     signal.signal(signal.SIGINT, exit_handler) | ||||||
|  |     signal.signal(signal.SIGTERM, exit_handler) | ||||||
|  | 
 | ||||||
|  |     sys.stdout.write("Start listening on %s:%d...\n" % Listen) | ||||||
|  |     sys.stdout.flush() | ||||||
|     server.serve_forever() |     server.serve_forever() | ||||||
|  |  | ||||||
|  | @ -22,8 +22,10 @@ http { | ||||||
|         location / { |         location / { | ||||||
|             auth_request /auth-proxy; |             auth_request /auth-proxy; | ||||||
| 
 | 
 | ||||||
|             # redirect 401 and 403 to login form |             # redirect 401 to login form | ||||||
|             error_page 401 403 =200 /login; |             # Comment them out if using HTTP basic authentication. | ||||||
|  |             # or authentication popup won't show | ||||||
|  |             error_page 401 =200 /login; | ||||||
| 
 | 
 | ||||||
|             proxy_pass http://backend/; |             proxy_pass http://backend/; | ||||||
|         } |         } | ||||||
|  | @ -45,15 +47,16 @@ http { | ||||||
|             proxy_pass http://127.0.0.1:8888; |             proxy_pass http://127.0.0.1:8888; | ||||||
| 
 | 
 | ||||||
|             proxy_pass_request_body off; |             proxy_pass_request_body off; | ||||||
|  |             proxy_pass_request_headers off; | ||||||
|             proxy_set_header Content-Length ""; |             proxy_set_header Content-Length ""; | ||||||
|             proxy_cache auth_cache; |             proxy_cache auth_cache; | ||||||
|             proxy_cache_valid 200 403 10m; |             proxy_cache_valid 200 10m; | ||||||
| 
 | 
 | ||||||
|             # The following directive adds the cookie to the cache key |             # The following directive adds the cookie to the cache key | ||||||
|             proxy_cache_key "$http_authorization$cookie_nginxauth"; |             proxy_cache_key "$http_authorization$cookie_nginxauth"; | ||||||
| 
 | 
 | ||||||
|             # As implemented in nginx-ldap-auth-daemon.py, the ldap-auth daemon |             # As implemented in nginx-ldap-auth-daemon.py, the ldap-auth daemon | ||||||
|             # communicates with an OpenLDAP server, passing in the following |             # communicates with a LDAP server, passing in the following | ||||||
|             # parameters to specify which user account to authenticate. To |             # parameters to specify which user account to authenticate. To | ||||||
|             # eliminate the need to modify the Python code, this file contains |             # eliminate the need to modify the Python code, this file contains | ||||||
|             # 'proxy_set_header' directives that set the values of the |             # 'proxy_set_header' directives that set the values of the | ||||||
|  | @ -61,17 +64,25 @@ http { | ||||||
|             # |             # | ||||||
|             #    Parameter      Proxy header |             #    Parameter      Proxy header | ||||||
|             #    -----------    ---------------- |             #    -----------    ---------------- | ||||||
|  |             #    url            X-Ldap-URL | ||||||
|  |             #    starttls       X-Ldap-Starttls | ||||||
|             #    basedn         X-Ldap-BaseDN |             #    basedn         X-Ldap-BaseDN | ||||||
|             #    binddn         X-Ldap-BindDN |             #    binddn         X-Ldap-BindDN | ||||||
|             #    bindpasswd     X-Ldap-BindPass |             #    bindpasswd     X-Ldap-BindPass | ||||||
|             #    cookiename     X-CookieName |             #    cookiename     X-CookieName | ||||||
|             #    realm          X-Ldap-Realm |             #    realm          X-Ldap-Realm | ||||||
|             #    template       X-Ldap-Template |             #    template       X-Ldap-Template | ||||||
|             #    url            X-Ldap-URL |  | ||||||
| 
 | 
 | ||||||
|             # (Required) Set the URL and port for connecting to the LDAP server, |             # (Required) Set the URL and port for connecting to the LDAP server, | ||||||
|             # by replacing 'example.com' and '636'. |             # by replacing 'example.com'. | ||||||
|             proxy_set_header X-Ldap-URL      "ldaps://example.com:636"; |             # Do not mix ldaps-style URL and X-Ldap-Starttls as it will not work. | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://example.com"; | ||||||
|  | 
 | ||||||
|  |             # (Optional) Establish a TLS-enabled LDAP session after binding to the | ||||||
|  |             # LDAP server. | ||||||
|  |             # This is the 'proper' way to establish encrypted TLS connections, see | ||||||
|  |             # http://www.openldap.org/faq/data/cache/185.html | ||||||
|  |             #proxy_set_header X-Ldap-Starttls "true"; | ||||||
| 
 | 
 | ||||||
|             # (Required) Set the Base DN, by replacing the value enclosed in |             # (Required) Set the Base DN, by replacing the value enclosed in | ||||||
|             # double quotes. |             # double quotes. | ||||||
|  | @ -91,9 +102,17 @@ http { | ||||||
|             proxy_set_header X-CookieName "nginxauth"; |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|             proxy_set_header Cookie nginxauth=$cookie_nginxauth; |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
| 
 | 
 | ||||||
|  |             # (Optional) Uncomment if using HTTP basic authentication | ||||||
|  |             #proxy_set_header Authorization $http_authorization; | ||||||
|  | 
 | ||||||
|             # (Required if using Microsoft Active Directory as the LDAP server) |             # (Required if using Microsoft Active Directory as the LDAP server) | ||||||
|             # Set the LDAP template by uncommenting the following directive. |             # Set the LDAP template by uncommenting the following directive. | ||||||
|             #proxy_set_header X-Ldap-Template "(SAMAccountName=%(username)s)"; |             #proxy_set_header X-Ldap-Template "(sAMAccountName=%(username)s)"; | ||||||
|  | 
 | ||||||
|  |             # (May be required if using Microsoft Active Directory and | ||||||
|  |             # getting "In order to perform this operation a successful bind | ||||||
|  |             # must be completed on the connection." errror) | ||||||
|  |             #proxy_set_header X-Ldap-DisableReferrals "true"; | ||||||
| 
 | 
 | ||||||
|             # (Optional if using OpenLDAP as the LDAP server) Set the LDAP |             # (Optional if using OpenLDAP as the LDAP server) Set the LDAP | ||||||
|             # template by uncommenting the following directive and replacing |             # template by uncommenting the following directive and replacing | ||||||
|  |  | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | # | ||||||
|  | # these are used with systemd too | ||||||
|  | # so please keep options names inside variables | ||||||
|  | # | ||||||
|  | #URL="--url ldap://example.com:389" | ||||||
|  | #BASE="-b dc=nodomain" | ||||||
|  | #BIND_DN="-D cn=admin,dc=nodomain" | ||||||
|  | #BIND_PASS="-w secret" | ||||||
|  | #COOKIE="-c nginxauth" | ||||||
|  | #FILTER="-f (cn=%(username)s)" | ||||||
|  | #REALM="-R 'Restricted Area'" | ||||||
|  | 
 | ||||||
|  | # these are used with init scripts only | ||||||
|  | LOG=/var/log/nginx-ldap-auth/daemon.log | ||||||
|  | RUNDIR=/var/run/nginx-ldap-auth/ | ||||||
|  | PIDFILE=/var/run/nginx-ldap-auth/nginx-ldap-auth.pid | ||||||
|  | USER=nginx-ldap-auth | ||||||
|  | GROUP=nginx-ldap-auth | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | /var/log/nginx-ldap-auth/daemon.log { | ||||||
|  | 	compress | ||||||
|  | 	delaycompress | ||||||
|  | 	create 0644 nginx-ldap-auth nginx-ldap-auth | ||||||
|  | 	su nginx-ldap-auth nginx-ldap-auth | ||||||
|  | 	postrotate | ||||||
|  | 		/usr/bin/systemctl restart nginx-ldap-auth | ||||||
|  | 	endscript | ||||||
|  | } | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | [Unit] | ||||||
|  | Description=LDAP authentication helper for Nginx | ||||||
|  | After=network.target network-online.target | ||||||
|  | 
 | ||||||
|  | [Service] | ||||||
|  | Type=simple | ||||||
|  | User=nginx-ldap-auth | ||||||
|  | Group=nginx-ldap-auth | ||||||
|  | WorkingDirectory=/var/run | ||||||
|  | EnvironmentFile=/etc/default/nginx-ldap-auth | ||||||
|  | ExecStart=/usr/bin/nginx-ldap-auth-daemon $URL $BASE $BIND_DN $BIND_PASS $COOKIE $FILTER $REALM | ||||||
|  | KillMode=process | ||||||
|  | KillSignal=SIGINT | ||||||
|  | Restart=on-failure | ||||||
|  | 
 | ||||||
|  | [Install] | ||||||
|  | WantedBy=multi-user.target | ||||||
|  | @ -0,0 +1,68 @@ | ||||||
|  | %global logdir  /var/log/%name | ||||||
|  | 
 | ||||||
|  | Name:		nginx-ldap-auth | ||||||
|  | Version:	0.0.5 | ||||||
|  | Release:	1%{?dist} | ||||||
|  | Summary:	NGINX Plus LDAP authentication daemon | ||||||
|  | 
 | ||||||
|  | Group:		System Environment/Daemons | ||||||
|  | License:	2-clause BSD-like license | ||||||
|  | URL:		https://github.com/nginxinc/nginx-ldap-auth | ||||||
|  | Source0:	nginx-ldap-auth-release-%{version}.tar.gz | ||||||
|  | 
 | ||||||
|  | BuildRequires:	systemd | ||||||
|  | Requires:	systemd | ||||||
|  | Requires:	python-ldap | ||||||
|  | Requires:	python-argparse | ||||||
|  | Requires:	logrotate | ||||||
|  | 
 | ||||||
|  | %description | ||||||
|  | Reference implementation of method for authenticating users on behalf of | ||||||
|  | servers proxied by NGINX or NGINX Plus. | ||||||
|  | 
 | ||||||
|  | %prep | ||||||
|  | %setup -q | ||||||
|  | 
 | ||||||
|  | %install | ||||||
|  | ls | ||||||
|  | mkdir -p %buildroot%_bindir | ||||||
|  | install -m755 nginx-ldap-auth-daemon.py %buildroot%_bindir/nginx-ldap-auth-daemon | ||||||
|  | mkdir -p %buildroot%_unitdir | ||||||
|  | install -m644 %name.service %buildroot%_unitdir/ | ||||||
|  | install -d -m755 %buildroot/etc/default | ||||||
|  | install -m644 %name.default %buildroot/etc/default/%name | ||||||
|  | install -d -m755 %buildroot/etc/logrotate.d | ||||||
|  | install -m644 %name.logrotate %buildroot%_sysconfdir/logrotate.d/%name | ||||||
|  | install -d -m755 %{buildroot}%{logdir} | ||||||
|  | 
 | ||||||
|  | %files | ||||||
|  | %doc README.md nginx-ldap-auth.conf backend-sample-app.py LICENSE | ||||||
|  | %config(noreplace) /etc/default/%name | ||||||
|  | %config(noreplace) %_sysconfdir/logrotate.d/%name | ||||||
|  | %_bindir/nginx-ldap-auth-daemon | ||||||
|  | %_unitdir/%name.service | ||||||
|  | %attr(750,nginx-ldap-auth,nginx-ldap-auth) %dir %{logdir} | ||||||
|  | 
 | ||||||
|  | %pre | ||||||
|  | getent group nginx-ldap-auth > /dev/null || groupadd -r nginx-ldap-auth | ||||||
|  | getent passwd nginx-ldap-auth > /dev/null || \ | ||||||
|  |     useradd -r -d /var/lib/nginx -g nginx-ldap-auth \ | ||||||
|  |     -s /sbin/nologin -c "Nginx auth helper" nginx-ldap-auth | ||||||
|  | 
 | ||||||
|  | %post | ||||||
|  | if [ $1 -eq 1 ]; then | ||||||
|  |     /usr/bin/systemctl preset nginx-ldap-auth.service >/dev/null 2>&1 ||: | ||||||
|  | fi; | ||||||
|  | 
 | ||||||
|  | %preun | ||||||
|  | if [ $1 -eq 0 ]; then | ||||||
|  |     /usr/bin/systemctl --no-reload disable nginx-ldap-auth.service >/dev/null 2>&1 ||: | ||||||
|  |     /usr/bin/systemctl stop nginx-ldap-auth.service >/dev/null 2>&1 ||: | ||||||
|  | fi; | ||||||
|  | 
 | ||||||
|  | %postun | ||||||
|  | /usr/bin/systemctl daemon-reload >/dev/null 2>&1 ||: | ||||||
|  | 
 | ||||||
|  | %changelog | ||||||
|  | * Wed Nov 02 2016 Konstantin Pavlov <thresh@nginx.com> 0.0.3-1 | ||||||
|  | - Initial release | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | To run tests use supplied Dockerfile.test: | ||||||
|  | 
 | ||||||
|  | ```shell | ||||||
|  | docker build -t my-tag -f Dockerfile.test . | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | If you desire to use a container with Python3, you can supply an appropriate | ||||||
|  | build argument: | ||||||
|  | 
 | ||||||
|  | ```shell | ||||||
|  | docker build -f Dockerfile.test -t my-tag --build-arg PYTHON_VERSION=3 . | ||||||
|  | docker run my-tag | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To run without Docker: | ||||||
|  | 
 | ||||||
|  | Test suite is available at http://hg.nginx.org/nginx-tests. | ||||||
|  | Check the http://hg.nginx.org/nginx-tests/file/tip/README file | ||||||
|  | for instructions on how to use it. | ||||||
|  | 
 | ||||||
|  | Additionally, the test requires a working installation | ||||||
|  | of OpenLDAP server and utilities (http://www.openldap.org/), | ||||||
|  | and python's coverage tool (https://coverage.readthedocs.io) | ||||||
|  | 
 | ||||||
|  | copy ldap-auth.t into testsuite, setup environment variables: | ||||||
|  | 
 | ||||||
|  | $ export TEST_LDAP_DAEMON=/usr/lib64/openldap/slapd | ||||||
|  | $ export TEST_LDAP_AUTH_DAEMON=/path/to/nginx-ldap-auth-daemon.py | ||||||
|  | $ prove 'ldap-auth.t' | ||||||
|  | 
 | ||||||
|  | to get coverage report: | ||||||
|  | 
 | ||||||
|  | $ export TEST_NGINX_LEAVE=1 | ||||||
|  | $ prove 'ldap-auth.t' | ||||||
|  | $ cd /tmp/nginx-test-xxxx | ||||||
|  | $ coverage2 html | ||||||
|  | 
 | ||||||
|  | report is now generated in htmlcov/ | ||||||
|  | @ -0,0 +1,595 @@ | ||||||
|  | #!/usr/bin/perl | ||||||
|  | 
 | ||||||
|  | # (C) Nginx, Inc. | ||||||
|  | 
 | ||||||
|  | # Test for nginx-ldap-auth daemon with OpenLDAP. | ||||||
|  | 
 | ||||||
|  | ############################################################################### | ||||||
|  | 
 | ||||||
|  | use warnings; | ||||||
|  | use strict; | ||||||
|  | 
 | ||||||
|  | use Test::More; | ||||||
|  | 
 | ||||||
|  | use MIME::Base64; | ||||||
|  | 
 | ||||||
|  | BEGIN { use FindBin; chdir($FindBin::Bin); } | ||||||
|  | 
 | ||||||
|  | use lib 'lib'; | ||||||
|  | use Test::Nginx; | ||||||
|  | 
 | ||||||
|  | ############################################################################### | ||||||
|  | 
 | ||||||
|  | select STDERR; $| = 1; | ||||||
|  | select STDOUT; $| = 1; | ||||||
|  | 
 | ||||||
|  | my $t = Test::Nginx->new()->has(qw/http proxy rewrite auth_request/) | ||||||
|  | 	->write_file_expand('nginx.conf', <<'EOF'); | ||||||
|  | 
 | ||||||
|  | %%TEST_GLOBALS%% | ||||||
|  | 
 | ||||||
|  | events { } | ||||||
|  | 
 | ||||||
|  | daemon off; | ||||||
|  | 
 | ||||||
|  | http { | ||||||
|  | 
 | ||||||
|  |     %%TEST_GLOBALS_HTTP%% | ||||||
|  | 
 | ||||||
|  |     #proxy_cache_path cache/  keys_zone=auth_cache:10m; | ||||||
|  | 
 | ||||||
|  |     server { | ||||||
|  |         listen 127.0.0.1:8082; | ||||||
|  | 
 | ||||||
|  |         location / { | ||||||
|  |             return 200 "ACCESS GRANTED\n"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /login { | ||||||
|  |             return 200 "LOGIN PAGE\n"; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     upstream backend { | ||||||
|  |         server 127.0.0.1:8082; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     server { | ||||||
|  |         listen 127.0.0.1:8080; | ||||||
|  | 
 | ||||||
|  |         location / { | ||||||
|  |             auth_request /auth-proxy; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /ssl { | ||||||
|  |             auth_request /auth-proxy-ssl; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /starttls { | ||||||
|  |             auth_request /auth-proxy-starttls; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /nodn { | ||||||
|  |             auth_request /auth-nodn; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /nourl { | ||||||
|  |             auth_request /auth-nourl; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /ref1 { | ||||||
|  |             auth_request /auth-ref1; | ||||||
|  | 
 | ||||||
|  |             error_page 401 =200 /login; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://backend/; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /query-injection { | ||||||
|  |             auth_request /auth-query-injection; | ||||||
|  | 	     | ||||||
|  | 	    error_page 401 =200 /login; | ||||||
|  | 	     | ||||||
|  | 	    proxy_pass http://backend/; | ||||||
|  |         | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location /login { | ||||||
|  |             proxy_pass http://backend/login; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Target $request_uri; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-proxy { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             #proxy_cache auth_cache; | ||||||
|  |             #proxy_cache_valid 200 10m; | ||||||
|  |             #proxy_cache_key "$http_authorization$cookie_nginxauth"; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://127.0.0.1:8083"; | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|  |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  | 
 | ||||||
|  |             #proxy_set_header X-Ldap-Starttls "true"; | ||||||
|  | 
 | ||||||
|  |             #proxy_set_header X-Ldap-Template "(sAMAccountName=%(username)s)"; | ||||||
|  |             #proxy_set_header X-Ldap-DisableReferrals "true"; | ||||||
|  | 
 | ||||||
|  |             #proxy_set_header X-Ldap-Template "(cn=%(username)s)"; | ||||||
|  |             #proxy_set_header X-Ldap-Realm    "Restricted"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-proxy-ssl { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldaps://127.0.0.1:8084"; | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|  |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  | 
 | ||||||
|  |             #proxy_set_header X-Ldap-Starttls "true"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |        location = /auth-proxy-starttls { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://127.0.0.1:8083"; | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|  |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-Starttls "true"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-nodn { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://127.0.0.1:8083"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-nourl { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-ref1 { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://127.0.0.1:8083"; | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|  |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         location = /auth-query-injection { | ||||||
|  |             internal; | ||||||
|  | 
 | ||||||
|  |             proxy_pass http://127.0.0.1:8888; | ||||||
|  | 
 | ||||||
|  |             proxy_pass_request_body off; | ||||||
|  |             proxy_set_header Content-Length ""; | ||||||
|  | 
 | ||||||
|  |             proxy_set_header X-Ldap-URL      "ldap://127.0.0.1:8083"; | ||||||
|  |             proxy_set_header X-Ldap-BaseDN   "ou=Users,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindDN   "cn=root,dc=test,dc=local"; | ||||||
|  |             proxy_set_header X-Ldap-BindPass "secret"; | ||||||
|  |             | ||||||
|  |             proxy_set_header X-CookieName "nginxauth"; | ||||||
|  |             proxy_set_header Cookie nginxauth=$cookie_nginxauth; | ||||||
|  | 	     | ||||||
|  |             proxy_set_header X-Ldap-Template '(|(&(memberOf=superadmin)(cn=%(username)s))(&(memberOf=admin)(cn=%(username)s)))'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | my $d = $t->testdir(); | ||||||
|  | 
 | ||||||
|  | $t->write_file('openssl.conf', <<EOF); | ||||||
|  | [ req ] | ||||||
|  | default_bits = 1024 | ||||||
|  | encrypt_key = no | ||||||
|  | distinguished_name = req_distinguished_name | ||||||
|  | [ req_distinguished_name ] | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | foreach my $name ('localhost') { | ||||||
|  | 	system('openssl req -x509 -new ' | ||||||
|  | 		. "-config $d/openssl.conf -subj /CN=$name/ " | ||||||
|  | 		. "-out $d/$name.crt -keyout $d/$name.key " | ||||||
|  | 		. ">>$d/openssl.out 2>&1") == 0 | ||||||
|  | 		or die "Can't create certificate for $name: $!\n"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | $t->write_file_expand("slapd.conf", <<"EOF"); | ||||||
|  | include /etc/openldap/schema/core.schema | ||||||
|  | include /etc/openldap/schema/cosine.schema | ||||||
|  | include /etc/openldap/schema/inetorgperson.schema | ||||||
|  | include /etc/openldap/schema/nis.schema | ||||||
|  | include /etc/openldap/schema/misc.schema | ||||||
|  | 
 | ||||||
|  | pidfile  $d/slapd.pid | ||||||
|  | argsfile $d/slapd.args | ||||||
|  | logfile $d/slapd.log | ||||||
|  | 
 | ||||||
|  | loglevel 256 64 | ||||||
|  | 
 | ||||||
|  | access to dn.base="" by * read | ||||||
|  | access to dn.base="cn=Subschema" by * read | ||||||
|  | access to * | ||||||
|  |   by self write | ||||||
|  |   by users read | ||||||
|  |   by anonymous read | ||||||
|  | 
 | ||||||
|  | moduleload     back_hdb | ||||||
|  | database hdb | ||||||
|  | suffix "dc=test,dc=local" | ||||||
|  | rootdn "cn=root,dc=test,dc=local" | ||||||
|  | rootpw secret | ||||||
|  | directory $d/openldap-data | ||||||
|  | index objectClass eq | ||||||
|  | 
 | ||||||
|  | TLSCipherSuite HIGH:MEDIUM:+SSLv2 | ||||||
|  | TLSCACertificateFile $d/localhost.crt | ||||||
|  | TLSCertificateFile $d/localhost.crt | ||||||
|  | TLSCertificateKeyFile $d/localhost.key | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | $t->write_file_expand("slapd2.conf", <<"EOF"); | ||||||
|  | include /etc/openldap/schema/core.schema | ||||||
|  | include /etc/openldap/schema/cosine.schema | ||||||
|  | include /etc/openldap/schema/inetorgperson.schema | ||||||
|  | include /etc/openldap/schema/nis.schema | ||||||
|  | include /etc/openldap/schema/misc.schema | ||||||
|  | 
 | ||||||
|  | pidfile  $d/slapd2.pid | ||||||
|  | argsfile $d/slapd2.args | ||||||
|  | logfile $d/slapd2.log | ||||||
|  | 
 | ||||||
|  | loglevel 256 64 | ||||||
|  | 
 | ||||||
|  | access to dn.base="" by * read | ||||||
|  | access to dn.base="cn=Subschema" by * read | ||||||
|  | access to * | ||||||
|  |   by self write | ||||||
|  |   by users read | ||||||
|  |   by anonymous read | ||||||
|  | 
 | ||||||
|  | moduleload     back_hdb | ||||||
|  | database hdb | ||||||
|  | suffix "ou=Users, dc=test,dc=local" | ||||||
|  | rootdn "cn=root, ou=Users, dc=test,dc=local" | ||||||
|  | rootpw secret | ||||||
|  | directory $d/openldap2-data | ||||||
|  | index objectClass eq | ||||||
|  | 
 | ||||||
|  | TLSCipherSuite HIGH:MEDIUM:+SSLv2 | ||||||
|  | TLSCACertificateFile $d/localhost.crt | ||||||
|  | TLSCertificateFile $d/localhost.crt | ||||||
|  | TLSCertificateKeyFile $d/localhost.key | ||||||
|  | 
 | ||||||
|  | # our upstream | ||||||
|  | referral   ldap://127.0.0.1:%%PORT_8083%%/ | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | $t->write_file_expand("initial.ldif", <<'EOF'); | ||||||
|  | dn: dc=test,dc=local | ||||||
|  | dc: test | ||||||
|  | description: Test-OU | ||||||
|  | objectClass: dcObject | ||||||
|  | objectClass: organization | ||||||
|  | o: Example, Inc. | ||||||
|  | 
 | ||||||
|  | dn: ou=Users, dc=test,dc=local | ||||||
|  | ou: Users | ||||||
|  | description: All people in organisation | ||||||
|  | objectclass: organizationalunit | ||||||
|  | 
 | ||||||
|  | dn: cn=user1,ou=Users,dc=test,dc=local | ||||||
|  | objectclass: inetOrgPerson | ||||||
|  | cn: User1 | ||||||
|  | sn: u1 | ||||||
|  | uid: user1 | ||||||
|  | userpassword: user1secret | ||||||
|  | mail: user1@example.com | ||||||
|  | description: user1 | ||||||
|  | ou: Users | ||||||
|  | 
 | ||||||
|  | dn: cn=user2,ou=Users,dc=test,dc=local | ||||||
|  | objectclass: inetOrgPerson | ||||||
|  | cn: User2 | ||||||
|  | sn: u2 | ||||||
|  | uid: user2 | ||||||
|  | userpassword: user2secret | ||||||
|  | mail: user2@example.com | ||||||
|  | description: user2 | ||||||
|  | ou: Users | ||||||
|  | 
 | ||||||
|  | dn: cn=user3,ou=Users,dc=test,dc=local | ||||||
|  | objectclass: inetOrgPerson | ||||||
|  | cn: User3 | ||||||
|  | sn: u3 | ||||||
|  | uid: user3 | ||||||
|  | userpassword: user3secret | ||||||
|  | mail: user3@example.com | ||||||
|  | description: user3 | ||||||
|  | ou: Users | ||||||
|  | 
 | ||||||
|  | dn: ou=more,ou=Users,dc=test,dc=local | ||||||
|  | objectClass: referral | ||||||
|  | objectClass: extensibleObject | ||||||
|  | dc: subtree | ||||||
|  | ref: ldap://127.0.0.1:%%PORT_8085%%/ou=more,ou=Users,dc=test,dc=local | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | $t->write_file_expand("initial2.ldif", <<'EOF'); | ||||||
|  | dn: ou=Users, dc=test,dc=local | ||||||
|  | ou: Users | ||||||
|  | description: All people in organisation | ||||||
|  | objectclass: organizationalunit | ||||||
|  | 
 | ||||||
|  | dn: ou=more,ou=Users,dc=test,dc=local | ||||||
|  | dc: test | ||||||
|  | description: Test-OU | ||||||
|  | objectClass: dcObject | ||||||
|  | objectClass: organizationalUnit | ||||||
|  | 
 | ||||||
|  | dn: cn=user4, ou=more, ou=Users,dc=test,dc=local | ||||||
|  | objectclass: inetOrgPerson | ||||||
|  | cn: User4 | ||||||
|  | sn: u4 | ||||||
|  | uid: user4 | ||||||
|  | userpassword: user4secret | ||||||
|  | mail: user4@example.com | ||||||
|  | description: user4 | ||||||
|  | ou: Users | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | # -u ldap -g ldap | ||||||
|  | my $SLAPD = defined $ENV{TEST_LDAP_DAEMON} ? $ENV{TEST_LDAP_DAEMON} | ||||||
|  | 	: '/usr/lib64/openldap/slapd'; | ||||||
|  | 
 | ||||||
|  | my $AUTHD = defined $ENV{TEST_LDAP_AUTH_DAEMON} ? $ENV{TEST_LDAP_AUTH_DAEMON} | ||||||
|  | 	: 'nginx-ldap-auth-daemon.py'; | ||||||
|  | 
 | ||||||
|  | $t->has_daemon($SLAPD); | ||||||
|  | $t->has_daemon($AUTHD); | ||||||
|  | 
 | ||||||
|  | mkdir("$d/openldap-data"); | ||||||
|  | mkdir("$d/openldap2-data"); | ||||||
|  | 
 | ||||||
|  | my $p3 = port(8083); | ||||||
|  | my $p4 = port(8084); | ||||||
|  | my $p5 = port(8085); | ||||||
|  | 
 | ||||||
|  | # change '0' to '1' or more to get debug from slapd | ||||||
|  | $t->run_daemon($SLAPD, '-d', '0', '-f', "$d/slapd.conf", | ||||||
|  | 		'-h', "ldap://127.0.0.1:$p3 ldaps://127.0.0.1:$p4"); | ||||||
|  | 
 | ||||||
|  | $t->run_daemon($SLAPD, '-d', '0', '-f', "$d/slapd2.conf", | ||||||
|  | 		'-h', "ldap://127.0.0.1:$p5"); | ||||||
|  | 
 | ||||||
|  | $t->waitforsocket("127.0.0.1:$p3") or die "Can't start slapd"; | ||||||
|  | $t->waitforsocket("127.0.0.1:$p5") or die "Can't start slapd2"; | ||||||
|  | 
 | ||||||
|  | system("ldapadd -H ldap://127.0.0.1:$p3 -x -D \"cn=root,dc=test,dc=local\"" | ||||||
|  |        . " -f $d/initial.ldif -w secret >> $d/ldif.log 2>&1") == 0 | ||||||
|  | 		or die "Can't import initial LDIF\n"; | ||||||
|  | 
 | ||||||
|  | system("ldapadd -H ldap://127.0.0.1:$p5 -x -D \"cn=root,ou=Users,dc=test,dc=local\"" | ||||||
|  |        . " -f $d/initial2.ldif -w secret >> $d/ldif2.log 2>&1") == 0 | ||||||
|  | 		or die "Can't import initial2 LDIF\n"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | $t->write_file_expand("auth_daemon.sh", <<"EOF"); | ||||||
|  | AUTHBIN=\$(realpath $AUTHD) | ||||||
|  | cd $d | ||||||
|  | exec coverage run \$AUTHBIN --host 127.0.0.1 \\ | ||||||
|  |     -p %%PORT_8888%% >$d/nginx-ldap-auth-dameon.stdlog 2>&1 | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  | $t->run_daemon('/bin/sh', "$d/auth_daemon.sh"); | ||||||
|  | $t->waitforsocket('127.0.0.1:' . port(8888)) | ||||||
|  | 	or die "Can't start auth daemon"; | ||||||
|  | 
 | ||||||
|  | $t->plan(22); | ||||||
|  | 
 | ||||||
|  | $t->run(); | ||||||
|  | 
 | ||||||
|  | ############################################################################### | ||||||
|  | 
 | ||||||
|  | like(http_get_auth('/', 'user1', 'user1secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'proper user with proper pass'); | ||||||
|  | like(http_get_auth('/', 'user1', 'randompass'), qr!LOGIN PAGE!, | ||||||
|  | 	'proper user with incorrect pass'); | ||||||
|  | like(http_get_auth('/', 'user111', 'user1secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'similar user with user1 pass'); | ||||||
|  | like(http_get_auth('/', 'randomuser', 'randompass'), qr!LOGIN PAGE!, | ||||||
|  | 	'random user with random pass'); | ||||||
|  | like(http_get_auth('/', 'user2', 'user2secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'user2 with proper pass'); | ||||||
|  | like(http_get_auth('/', 'user3', 'user3secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'user3 with proper pass'); | ||||||
|  | like(http_get_auth('/', '', ''), qr!LOGIN PAGE!, 'empty user no password'); | ||||||
|  | like(http_get('/'), qr!LOGIN PAGE!, 'no auth header'); | ||||||
|  | 
 | ||||||
|  | like(http_get_cookie('/', 'user1', 'user1secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'proper user with proper pass cookie'); | ||||||
|  | like(http_get_cookie('/', 'user1', 'randompasz'), qr!LOGIN PAGE!, | ||||||
|  | 	'proper user with incorrect pass cookie'); | ||||||
|  | like(http_get_cookie('/', 'randomuser', 'randompass'), qr!LOGIN PAGE!, | ||||||
|  | 	'random user with random pass cookie'); | ||||||
|  | like(http_get_cookie('/', 'user2', 'user2secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'user2 with proper pass cookie'); | ||||||
|  | like(http_get_cookie('/', 'user3', 'user3secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'user3 with proper pass cookie'); | ||||||
|  | 
 | ||||||
|  | like(http_get_auth_broken_base64('/', 'user3', 'user3secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'user3 with proper pass broken base64'); | ||||||
|  | like(http_get_cookie_broken_base64('/', 'user3', 'user3secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'user3 with proper pass broken cookie'); | ||||||
|  | 
 | ||||||
|  | like(http_get_auth('/ssl', 'user1', 'user1secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'proper user with proper pass with ssl'); | ||||||
|  | 
 | ||||||
|  | like(http_get_auth('/starttls', 'user1', 'user1secret'), qr!ACCESS GRANTED!, | ||||||
|  | 	'proper user with proper pass with starttls'); | ||||||
|  | 
 | ||||||
|  | # dn is not set, no default, daemon error => 502 | ||||||
|  | like(http_get_auth('/nodn', 'user1', 'user1secret'), qr!Internal Server Error!, | ||||||
|  | 	'dn must be set'); | ||||||
|  | 
 | ||||||
|  | # url is not set, default is used, which is not accessible => login page | ||||||
|  | like(http_get_auth('/nourl', 'user1', 'user1secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'url must be set'); | ||||||
|  | 
 | ||||||
|  | # LDAP referrals | ||||||
|  | 
 | ||||||
|  | # user can be found, but bind happens on 1st server, instead of the found | ||||||
|  | # the behaviour may change with different servers | ||||||
|  | like(http_get_auth('/ref1', 'user4', 'user4secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'server2 user via referral on server1'); | ||||||
|  | 
 | ||||||
|  | # unknown user on referred server, result is empty dn | ||||||
|  | like(http_get_auth('/ref1', 'unknow_user', 'unknowpassword'), qr!LOGIN PAGE!, | ||||||
|  | 	'unknown user with referral on server1'); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # LDAP Query Injection result in 401 | ||||||
|  | like(http_get_auth('/query-injection', 'user1))(|(cn=user1', 'user1secret'), qr!LOGIN PAGE!, | ||||||
|  | 	'Injection Attempt in Username will be escaped and blocked.'); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ############################################################################### | ||||||
|  | 
 | ||||||
|  | sub http_get_auth { | ||||||
|  | 	my ($url, $user, $password) = @_; | ||||||
|  | 
 | ||||||
|  | 	my $auth = encode_base64($user . ':' . $password, ''); | ||||||
|  | 
 | ||||||
|  | 	return http(<<EOF); | ||||||
|  | GET $url HTTP/1.0 | ||||||
|  | Host: localhost | ||||||
|  | Authorization: Basic $auth | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | # do not encode auth with base64, send plain | ||||||
|  | sub http_get_auth_broken_base64 { | ||||||
|  | 	my ($url, $user, $password) = @_; | ||||||
|  | 
 | ||||||
|  | 	my $auth = $user . ':' . $password; | ||||||
|  | 
 | ||||||
|  | 	return http(<<EOF); | ||||||
|  | GET $url HTTP/1.0 | ||||||
|  | Host: localhost | ||||||
|  | Authorization: Basic $auth | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | sub http_get_cookie { | ||||||
|  | 	my ($url, $user, $password) = @_; | ||||||
|  | 
 | ||||||
|  | 	my $auth = encode_base64($user . ':' . $password, ''); | ||||||
|  | 
 | ||||||
|  | 	return http(<<EOF); | ||||||
|  | GET $url HTTP/1.0 | ||||||
|  | Host: localhost | ||||||
|  | Cookie: nginxauth=$auth | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | sub http_get_cookie_broken_base64 { | ||||||
|  | 	my ($url, $user, $password) = @_; | ||||||
|  | 
 | ||||||
|  | 	my $auth = $user . ':' . $password; | ||||||
|  | 
 | ||||||
|  | 	return http(<<EOF); | ||||||
|  | GET $url HTTP/1.0 | ||||||
|  | Host: localhost | ||||||
|  | Cookie: nginxauth=$auth | ||||||
|  | 
 | ||||||
|  | EOF | ||||||
|  | } | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | #!/bin/sh | ||||||
|  | 
 | ||||||
|  | # shell script to start testsuite and run coverage | ||||||
|  | # to be executed as Dockerfile CMD | ||||||
|  | 
 | ||||||
|  | export TEST_NGINX_LEAVE=1 | ||||||
|  | rm -rf /tmp/nginx-test-* | ||||||
|  | 
 | ||||||
|  | perl ldap-auth.t | ||||||
|  | 
 | ||||||
|  | testdir=$(find /tmp -name 'nginx-test-*' -print -quit) | ||||||
|  | cd $testdir | ||||||
|  | coverage html && printf "Coverage report: docker cp <cid>:$testdir/htmlcov <hostdir>\n" | ||||||
		Loading…
	
		Reference in New Issue