├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .travis.yml.patch ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-MPL-RabbitMQ ├── Makefile ├── README-authorisation.md ├── README.md ├── TESTING.md ├── erlang.mk ├── example ├── README.md ├── global.ldif ├── memberof_init.ldif ├── refint_1.ldif └── refint_2.ldif ├── priv └── schema │ └── rabbitmq_auth_backend_ldap.schema ├── rabbitmq-components.mk ├── src ├── rabbit_auth_backend_ldap.erl ├── rabbit_auth_backend_ldap_app.erl └── rabbit_auth_backend_ldap_util.erl └── test ├── config_schema_SUITE.erl ├── config_schema_SUITE_data ├── certs │ ├── cacert.pem │ ├── cert.pem │ └── key.pem └── rabbitmq_auth_backend_ldap.snippets ├── rabbit_ldap_seed.erl ├── system_SUITE.erl ├── system_SUITE_data └── init-slapd.sh └── unit_SUITE.erl /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for using RabbitMQ. 2 | 3 | **STOP NOW AND READ THIS** BEFORE OPENING A NEW ISSUE ON GITHUB 4 | 5 | Unless you are CERTAIN you have found a reproducible problem in RabbitMQ or 6 | have a **specific, actionable** suggestion for our team, you must first ask 7 | your question or discuss your suspected issue on the mailing list: 8 | 9 | https://groups.google.com/forum/#!forum/rabbitmq-users 10 | 11 | Team RabbitMQ does not use GitHub issues for discussions, investigations, root 12 | cause analysis and so on. 13 | 14 | Please take the time to read the CONTRIBUTING.md document for instructions on 15 | how to effectively ask a question or report a suspected issue: 16 | 17 | https://github.com/rabbitmq/rabbitmq-server/blob/master/CONTRIBUTING.md#github-issues 18 | 19 | Following these rules **will save time** for both you and RabbitMQ's maintainers. 20 | 21 | Thank you. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Please describe the big picture of your changes here to communicate to the 4 | RabbitMQ team why we should accept this pull request. If it fixes a bug or 5 | resolves a feature request, be sure to link to that issue. 6 | 7 | A pull request that doesn't explain **why** the change was made has a much 8 | lower chance of being accepted. 9 | 10 | If English isn't your first language, don't worry about it and try to 11 | communicate the problem you are trying to solve to the best of your abilities. 12 | As long as we can understand the intent, it's all good. 13 | 14 | ## Types of Changes 15 | 16 | What types of changes does your code introduce to this project? 17 | _Put an `x` in the boxes that apply_ 18 | 19 | - [ ] Bug fix (non-breaking change which fixes issue #NNNN) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause an observable behavior change in existing systems) 22 | - [ ] Documentation improvements (corrections, new content, etc) 23 | - [ ] Cosmetic change (whitespace, formatting, etc) 24 | 25 | ## Checklist 26 | 27 | _Put an `x` in the boxes that apply. You can also fill these out after creating 28 | the PR. If you're unsure about any of them, don't hesitate to ask on the 29 | mailing list. We're here to help! This is simply a reminder of what we are 30 | going to look for before merging your code._ 31 | 32 | - [ ] I have read the `CONTRIBUTING.md` document 33 | - [ ] I have signed the CA (see https://cla.pivotal.io/sign/rabbitmq) 34 | - [ ] All tests pass locally with my changes 35 | - [ ] I have added tests that prove my fix is effective or that my feature works 36 | - [ ] I have added necessary documentation (if appropriate) 37 | - [ ] Any dependent changes have been merged and published in related repositories 38 | 39 | ## Further Comments 40 | 41 | If this is a relatively large or complex change, kick off the discussion by 42 | explaining why you chose the solution you did and what alternatives you 43 | considered, etc. 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sw? 2 | .*.sw? 3 | *.beam 4 | .vagrant 5 | /.erlang.mk/ 6 | /cover/ 7 | /deps/ 8 | /doc/ 9 | /ebin/ 10 | /escript/ 11 | /escript.lock 12 | /logs/ 13 | /plugins/ 14 | /plugins.lock 15 | /sbin/ 16 | /sbin.lock 17 | 18 | test/config_schema_SUITE_data/schema/ 19 | 20 | rabbitmq_auth_backend_ldap.d 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim:sw=2:et: 2 | 3 | os: linux 4 | dist: xenial 5 | language: elixir 6 | notifications: 7 | email: 8 | recipients: 9 | - alerts@rabbitmq.com 10 | on_success: never 11 | on_failure: always 12 | addons: 13 | apt: 14 | packages: 15 | - awscli 16 | - slapd 17 | - ldap-utils 18 | cache: 19 | apt: true 20 | env: 21 | global: 22 | - secure: S0N4NmNvsfbdfxe78MCxPFXX1NXhbpG4FVViCPTdKJUQHaWsaHnAUke95ItXUVB5i5ct3ogxNZk0fYXdbIjMgI/cS4L+/ASJ4MhhdddM80lwPjdbgC6+AXzM5ncYbdLqjZlKM5If4VnL7LqVK9G3PxavuuGTi8Idnn57CWtrI3g= 23 | - secure: C3B5R4FQ8iePMzhqq75BY2E2FVD1rZPAzTgEhZ+psgPvioyjXRIriHpFOMrqwQcPPpjdsHizpuSG9/SNHtjitw7uCB/Rnh8cyTnukZf7U4xPM37vKGq4HTYN8ABxjPyQGCWbS6TRXOf4Acp5qw8U87hmGmOlskTmnFinciAIvKA= 24 | 25 | # $base_rmq_ref is used by rabbitmq-components.mk to select the 26 | # appropriate branch for dependencies. 27 | - base_rmq_ref=master 28 | 29 | elixir: 30 | - '1.9' 31 | otp_release: 32 | - '21.3' 33 | - '22.2' 34 | 35 | install: 36 | # This project being an Erlang one (we just set language to Elixir 37 | # to ensure it is installed), we don't want Travis to run mix(1) 38 | # automatically as it will break. 39 | skip 40 | 41 | script: 42 | # $current_rmq_ref is also used by rabbitmq-components.mk to select 43 | # the appropriate branch for dependencies. 44 | - make check-rabbitmq-components.mk 45 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 46 | - make xref 47 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 48 | - make tests 49 | current_rmq_ref="${TRAVIS_PULL_REQUEST_BRANCH:-${TRAVIS_BRANCH}}" 50 | 51 | after_failure: 52 | - | 53 | cd "$TRAVIS_BUILD_DIR" 54 | if test -d logs && test "$AWS_ACCESS_KEY_ID" && test "$AWS_SECRET_ACCESS_KEY"; then 55 | archive_name="$(basename "$TRAVIS_REPO_SLUG")-$TRAVIS_JOB_NUMBER" 56 | 57 | tar -c --transform "s/^logs/${archive_name}/" -f - logs | \ 58 | xz > "${archive_name}.tar.xz" 59 | 60 | aws s3 cp "${archive_name}.tar.xz" s3://server-release-pipeline/travis-ci-logs/ \ 61 | --region eu-west-1 \ 62 | --acl public-read 63 | fi 64 | -------------------------------------------------------------------------------- /.travis.yml.patch: -------------------------------------------------------------------------------- 1 | --- .travis.yml.orig 2020-03-26 15:55:03.107179000 +0100 2 | +++ .travis.yml 2020-03-26 15:55:22.294603000 +0100 3 | @@ -13,6 +13,8 @@ 4 | apt: 5 | packages: 6 | - awscli 7 | + - slapd 8 | + - ldap-utils 9 | cache: 10 | apt: true 11 | env: 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open 4 | and welcoming community, we pledge to respect all people who contribute through reporting 5 | issues, posting feature requests, updating documentation, submitting pull requests or 6 | patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for 9 | everyone, regardless of level of experience, gender, gender identity and expression, 10 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, 11 | religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic addresses, 20 | without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 24 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 25 | Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors 26 | that they deem inappropriate, threatening, offensive, or harmful. 27 | 28 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and 29 | consistently applying these principles to every aspect of managing this project. Project 30 | maintainers who do not follow or enforce the Code of Conduct may be permanently removed 31 | from the project team. 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an 34 | individual is representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 37 | contacting a project maintainer at [info@rabbitmq.com](mailto:info@rabbitmq.com). All complaints will 38 | be reviewed and investigated and will result in a response that is deemed necessary and 39 | appropriate to the circumstances. Maintainers are obligated to maintain confidentiality 40 | with regard to the reporter of an incident. 41 | 42 | This Code of Conduct is adapted from the 43 | [Contributor Covenant](https://contributor-covenant.org), version 1.3.0, available at 44 | [contributor-covenant.org/version/1/3/0/](https://contributor-covenant.org/version/1/3/0/) 45 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for using RabbitMQ and for taking the time to contribute to the project. 2 | This document has two main parts: 3 | 4 | * when and how to file GitHub issues for RabbitMQ projects 5 | * how to submit pull requests 6 | 7 | They intend to save you and RabbitMQ maintainers some time, so please 8 | take a moment to read through them. 9 | 10 | ## Overview 11 | 12 | ### GitHub issues 13 | 14 | The RabbitMQ team uses GitHub issues for _specific actionable items_ that 15 | engineers can work on. This assumes the following: 16 | 17 | * GitHub issues are not used for questions, investigations, root cause 18 | analysis, discussions of potential issues, etc (as defined by this team) 19 | * Enough information is provided by the reporter for maintainers to work with 20 | 21 | The team receives many questions through various venues every single 22 | day. Frequently, these questions do not include the necessary details 23 | the team needs to begin useful work. GitHub issues can very quickly 24 | turn into a something impossible to navigate and make sense 25 | of. Because of this, questions, investigations, root cause analysis, 26 | and discussions of potential features are all considered to be 27 | [mailing list][rmq-users] material. If you are unsure where to begin, 28 | the [RabbitMQ users mailing list][rmq-users] is the right place. 29 | 30 | Getting all the details necessary to reproduce an issue, make a 31 | conclusion or even form a hypothesis about what's happening can take a 32 | fair amount of time. Please help others help you by providing a way to 33 | reproduce the behavior you're observing, or at least sharing as much 34 | relevant information as possible on the [RabbitMQ users mailing 35 | list][rmq-users]. 36 | 37 | Please provide versions of the software used: 38 | 39 | * RabbitMQ server 40 | * Erlang 41 | * Operating system version (and distribution, if applicable) 42 | * All client libraries used 43 | * RabbitMQ plugins (if applicable) 44 | 45 | The following information greatly helps in investigating and reproducing issues: 46 | 47 | * RabbitMQ server logs 48 | * A code example or terminal transcript that can be used to reproduce 49 | * Full exception stack traces (a single line message is not enough!) 50 | * `rabbitmqctl report` and `rabbitmqctl environment` output 51 | * Other relevant details about the environment and workload, e.g. a traffic capture 52 | * Feel free to edit out hostnames and other potentially sensitive information. 53 | 54 | To make collecting much of this and other environment information, use 55 | the [`rabbitmq-collect-env`][rmq-collect-env] script. It will produce an archive with 56 | server logs, operating system logs, output of certain diagnostics commands and so on. 57 | Please note that **no effort is made to scrub any information that may be sensitive**. 58 | 59 | ### Pull Requests 60 | 61 | RabbitMQ projects use pull requests to discuss, collaborate on and accept code contributions. 62 | Pull requests is the primary place of discussing code changes. 63 | 64 | Here's the recommended workflow: 65 | 66 | * [Fork the repository][github-fork] or repositories you plan on contributing to. If multiple 67 | repositories are involved in addressing the same issue, please use the same branch name 68 | in each repository 69 | * Create a branch with a descriptive name in the relevant repositories 70 | * Make your changes, run tests (usually with `make tests`), commit with a 71 | [descriptive message][git-commit-msgs], push to your fork 72 | * Submit pull requests with an explanation what has been changed and **why** 73 | * Submit a filled out and signed [Contributor Agreement][ca-agreement] if needed (see below) 74 | * Be patient. We will get to your pull request eventually 75 | 76 | If what you are going to work on is a substantial change, please first 77 | ask the core team for their opinion on the [RabbitMQ users mailing list][rmq-users]. 78 | 79 | ## Running Tests 80 | 81 | make tests 82 | 83 | ## Code of Conduct 84 | 85 | See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). 86 | 87 | ## Contributor Agreement 88 | 89 | If you want to contribute a non-trivial change, please submit a signed 90 | copy of our [Contributor Agreement][ca-agreement] around the time you 91 | submit your pull request. This will make it much easier (in some 92 | cases, possible) for the RabbitMQ team at Pivotal to merge your 93 | contribution. 94 | 95 | ## Where to Ask Questions 96 | 97 | If something isn't clear, feel free to ask on our [mailing list][rmq-users]. 98 | 99 | [rmq-collect-env]: https://github.com/rabbitmq/support-tools/blob/master/scripts/rabbitmq-collect-env 100 | [git-commit-msgs]: https://chris.beams.io/posts/git-commit/ 101 | [rmq-users]: https://groups.google.com/forum/#!forum/rabbitmq-users 102 | [ca-agreement]: https://cla.pivotal.io/sign/rabbitmq 103 | [github-fork]: https://help.github.com/articles/fork-a-repo/ 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This package is licensed under the MPL 2.0. For the MPL 2.0, please see LICENSE-MPL-RabbitMQ. 2 | 3 | If you have any questions regarding licensing, please contact us at 4 | info@rabbitmq.com. 5 | -------------------------------------------------------------------------------- /LICENSE-MPL-RabbitMQ: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = rabbitmq_auth_backend_ldap 2 | PROJECT_DESCRIPTION = RabbitMQ LDAP Authentication Backend 3 | PROJECT_MOD = rabbit_auth_backend_ldap_app 4 | 5 | # Note: 6 | # Use of these default values in calls to get_expected_env_str/2 7 | define PROJECT_ENV 8 | [ 9 | {servers, undefined}, 10 | {user_bind_pattern, none}, 11 | {user_dn_pattern, "$${username}"}, 12 | {dn_lookup_attribute, none}, 13 | {dn_lookup_base, none}, 14 | {group_lookup_base, none}, 15 | {dn_lookup_bind, as_user}, 16 | {other_bind, as_user}, 17 | {anon_auth, false}, 18 | {vhost_access_query, {constant, true}}, 19 | {resource_access_query, {constant, true}}, 20 | {topic_access_query, {constant, true}}, 21 | {tag_queries, [{administrator, {constant, false}}]}, 22 | {use_ssl, false}, 23 | {use_starttls, false}, 24 | {ssl_options, []}, 25 | {port, 389}, 26 | {timeout, infinity}, 27 | {log, false}, 28 | {pool_size, 64}, 29 | {idle_timeout, 300000} 30 | ] 31 | endef 32 | 33 | define PROJECT_APP_EXTRA_KEYS 34 | {broker_version_requirements, []} 35 | endef 36 | 37 | LOCAL_DEPS = eldap 38 | DEPS = rabbit_common rabbit 39 | TEST_DEPS = ct_helper rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client 40 | dep_ct_helper = git https://github.com/extend/ct_helper.git master 41 | 42 | DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk 43 | DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk 44 | 45 | # FIXME: Use erlang.mk patched for RabbitMQ, while waiting for PRs to be 46 | # reviewed and merged. 47 | 48 | ERLANG_MK_REPO = https://github.com/rabbitmq/erlang.mk.git 49 | ERLANG_MK_COMMIT = rabbitmq-tmp 50 | 51 | include rabbitmq-components.mk 52 | include erlang.mk 53 | -------------------------------------------------------------------------------- /README-authorisation.md: -------------------------------------------------------------------------------- 1 | See [RabbitMQ LDAP authentication backend docs](https://www.rabbitmq.com/ldap.html). 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RabbitMQ LDAP Authentication Backend 2 | 3 | ## This was migrated to https://github.com/rabbitmq/rabbitmq-server 4 | 5 | This repository has been moved to the main unified RabbitMQ "monorepo", including all open issues. You can find the source under [/deps/rabbitmq_auth_backend_ldap](https://github.com/rabbitmq/rabbitmq-server/tree/master/deps/rabbitmq_auth_backend_ldap). 6 | All issues have been transferred. 7 | 8 | ## Overview 9 | 10 | This plugin provides [authentication and authorisation backends](https://rabbitmq.com/access-control.html) 11 | for RabbitMQ that use LDAP. 12 | 13 | Under a heavy load this plugin can put a higher than expected amount of load on it's backing LDAP service. 14 | We recommend using it together with [rabbitmq_auth_backend_cache](https://github.com/rabbitmq/rabbitmq-auth-backend-cache) 15 | with a reasonable caching interval (e.g. 2-3 minutes). 16 | 17 | ## Installation 18 | 19 | This plugin ships with reasonably recent RabbitMQ versions 20 | (e.g. `3.3.0` or later). Enable it with 21 | 22 | rabbitmq-plugins enable rabbitmq_auth_backend_ldap 23 | 24 | ## Documentation 25 | 26 | [See LDAP guide](https://www.rabbitmq.com/ldap.html) on rabbitmq.com. 27 | 28 | 29 | ## Building from Source 30 | 31 | See [Plugin Development guide](https://www.rabbitmq.com/plugin-development.html). 32 | 33 | TL;DR: running 34 | 35 | make dist 36 | 37 | will build the plugin and put build artifacts under the `./plugins` directory. 38 | 39 | 40 | ## Copyright and License 41 | 42 | (c) 2007-2020 VMware, Inc. or its affiliates. 43 | 44 | Released under the MPL, the same license as RabbitMQ. 45 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Running Tests 2 | 3 | ## The OpenLDAP Dependency 4 | 5 | The testsuite depends on an OpenLDAP server. slapd(8) and the CLI must 6 | be installed for it to work. However there is no need to configure the 7 | server. 8 | 9 | The testsuite takes care of starting its own server and configuring it. 10 | It won't conflict with the system OpenLDAP server instance. 11 | 12 | The testsuite needs the following modules to be available: 13 | * `bdb` 14 | * `memberof` 15 | * `refint` 16 | 17 | ## Running All Suites 18 | 19 | As with all other RabbitMQ subprojects, 20 | 21 | ``` sh 22 | make tests 23 | ``` 24 | 25 | will run all test suites. 26 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | LDIFs files come from the following article: 2 | https://technicalnotes.wordpress.com/2014/04/19/openldap-setup-with-memberof-overlay/ 3 | -------------------------------------------------------------------------------- /example/global.ldif: -------------------------------------------------------------------------------- 1 | # Load modules for database type 2 | dn: cn=module,cn=config 3 | objectclass: olcModuleList 4 | cn: module 5 | olcModuleLoad: back_bdb.la 6 | 7 | # Create directory database 8 | dn: olcDatabase=bdb,cn=config 9 | objectClass: olcDatabaseConfig 10 | objectClass: olcBdbConfig 11 | olcDatabase: bdb 12 | # Domain name (e.g. rabbitmq.com) 13 | olcSuffix: dc=rabbitmq,dc=com 14 | # Location on system where database is stored 15 | olcDbDirectory: /var/lib/ldap 16 | # Manager of the database 17 | olcRootDN: cn=admin,dc=rabbitmq,dc=com 18 | olcRootPW: admin 19 | olcAccess: to attrs=userPassword 20 | by self write 21 | by anonymous auth 22 | by dn.base="cn=admin,dc=rabbitmq,dc=com" write 23 | by * none 24 | olcAccess: to * 25 | by self write 26 | by dn.base="cn=admin,dc=rabbitmq,dc=com" write 27 | by * read 28 | -------------------------------------------------------------------------------- /example/memberof_init.ldif: -------------------------------------------------------------------------------- 1 | dn: cn=module,cn=config 2 | cn: module 3 | objectClass: olcModuleList 4 | olcModuleLoad: memberof 5 | olcModulePath: /usr/lib/ldap 6 | 7 | dn: olcOverlay={0}memberof,olcDatabase={1}bdb,cn=config 8 | objectClass: olcConfig 9 | objectClass: olcMemberOf 10 | objectClass: olcOverlayConfig 11 | objectClass: top 12 | olcOverlay: memberof 13 | olcMemberOfDangling: ignore 14 | olcMemberOfRefInt: TRUE 15 | olcMemberOfGroupOC: groupOfNames 16 | olcMemberOfMemberAD: member 17 | olcMemberOfMemberOfAD: memberOf 18 | -------------------------------------------------------------------------------- /example/refint_1.ldif: -------------------------------------------------------------------------------- 1 | dn: cn=module{1},cn=config 2 | add: olcmoduleload 3 | olcmoduleload: refint -------------------------------------------------------------------------------- /example/refint_2.ldif: -------------------------------------------------------------------------------- 1 | dn: olcOverlay={1}refint,olcDatabase={1}bdb,cn=config 2 | objectClass: olcConfig 3 | objectClass: olcOverlayConfig 4 | objectClass: olcRefintConfig 5 | objectClass: top 6 | olcOverlay: {1}refint 7 | olcRefintAttribute: memberof member manager owner 8 | -------------------------------------------------------------------------------- /priv/schema/rabbitmq_auth_backend_ldap.schema: -------------------------------------------------------------------------------- 1 | %% ---------------------------------------------------------------------------- 2 | %% RabbitMQ LDAP Plugin 3 | %% 4 | %% See https://www.rabbitmq.com/ldap.html for details. 5 | %% 6 | %% ---------------------------------------------------------------------------- 7 | 8 | % {rabbitmq_auth_backend_ldap, 9 | % [ 10 | %% 11 | %% Connecting to the LDAP server(s) 12 | %% ================================ 13 | %% 14 | 15 | %% Specify servers to bind to. You *must* set this in order for the plugin 16 | %% to work properly. 17 | %% 18 | %% {servers, ["your-server-name-goes-here"]}, 19 | 20 | {mapping, "auth_ldap.servers", "rabbitmq_auth_backend_ldap.servers", 21 | [{datatype, {enum, [none]}}]}. 22 | 23 | {mapping, "auth_ldap.servers.$server", "rabbitmq_auth_backend_ldap.servers", 24 | [{datatype, string}]}. 25 | 26 | {translation, "rabbitmq_auth_backend_ldap.servers", 27 | fun(Conf) -> 28 | case cuttlefish:conf_get("auth_ldap.servers", Conf, undefined) of 29 | none -> []; 30 | _ -> 31 | Settings = cuttlefish_variable:filter_by_prefix("auth_ldap.servers", Conf), 32 | [ V || {_, V} <- Settings ] 33 | end 34 | end}. 35 | 36 | %% Specify the LDAP port to connect to 37 | %% 38 | %% {port, 389}, 39 | 40 | {mapping, "auth_ldap.port", "rabbitmq_auth_backend_ldap.port", 41 | [{datatype, integer}]}. 42 | 43 | %% LDAP connection/worker pool size 44 | %% 45 | %% {pool_size, 64}, 46 | 47 | {mapping, "auth_ldap.connection_pool_size", "rabbitmq_auth_backend_ldap.pool_size", 48 | [{datatype, integer}]}. 49 | 50 | %% LDAP connection timeout, in milliseconds or 'infinity' 51 | %% 52 | %% {timeout, infinity}, 53 | 54 | {mapping, "auth_ldap.timeout", "rabbitmq_auth_backend_ldap.timeout", 55 | [{datatype, [integer, {atom, infinity}]}]}. 56 | 57 | %% LDAP connection inactivity timeout, in milliseconds or 'infinity' 58 | %% 59 | %% {idle_timeout, 300000}, 60 | 61 | {mapping, "auth_ldap.idle_timeout", "rabbitmq_auth_backend_ldap.idle_timeout", 62 | [{datatype, [integer, {atom, infinity}]}]}. 63 | 64 | %% Enable logging of LDAP queries. 65 | %% One of 66 | %% - false (no logging is performed) 67 | %% - true (verbose logging of the logic used by the plugin) 68 | %% - network (as true, but additionally logs LDAP network traffic) 69 | %% - network_unsafe (won't try to scrub any credentials) 70 | %% 71 | %% Defaults to false. 72 | %% 73 | %% {log, false}, 74 | 75 | {mapping, "auth_ldap.log", "rabbitmq_auth_backend_ldap.log", 76 | [{datatype, {enum, [true, false, network, network_unsafe]}}]}. 77 | 78 | %% 79 | %% Authentication 80 | %% ============== 81 | %% 82 | 83 | %% Pattern to convert the username given through AMQP to a different 84 | %% form before performing a simple bind 85 | %% 86 | %% {user_bind_pattern, "${ad_user}@${ad_domain}.com"}, 87 | 88 | {mapping, "auth_ldap.user_bind_pattern", "rabbitmq_auth_backend_ldap.user_bind_pattern", 89 | [{datatype, string}]}. 90 | 91 | %% Pattern to convert the username given through AMQP to a DN before 92 | %% binding 93 | %% 94 | %% {user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"}, 95 | 96 | {mapping, "auth_ldap.user_dn_pattern", "rabbitmq_auth_backend_ldap.user_dn_pattern", 97 | [{datatype, string}]}. 98 | 99 | %% Alternatively, you can convert a username to a Distinguished 100 | %% Name via an LDAP lookup after binding. See the documentation for 101 | %% full details. 102 | 103 | %% When converting a username to a dn via a lookup, set these to 104 | %% the name of the attribute that represents the user name, and the 105 | %% base DN for the lookup query. 106 | %% 107 | %% {dn_lookup_attribute, "userPrincipalName"}, 108 | %% {dn_lookup_base, "DC=gopivotal,DC=com"}, 109 | 110 | {mapping, "auth_ldap.dn_lookup_attribute", "rabbitmq_auth_backend_ldap.dn_lookup_attribute", 111 | [{datatype, [{enum, [none]}, string]}]}. 112 | 113 | {mapping, "auth_ldap.dn_lookup_base", "rabbitmq_auth_backend_ldap.dn_lookup_base", 114 | [{datatype, [{enum, [none]}, string]}]}. 115 | 116 | {mapping, "auth_ldap.dn_lookup_bind", "rabbitmq_auth_backend_ldap.dn_lookup_bind", 117 | [{datatype, [{enum, [as_user, anon]}]}]}. 118 | 119 | {mapping, "auth_ldap.dn_lookup_bind.user_dn", "rabbitmq_auth_backend_ldap.dn_lookup_bind", 120 | [{datatype, [string]}]}. 121 | 122 | {mapping, "auth_ldap.dn_lookup_bind.password", "rabbitmq_auth_backend_ldap.dn_lookup_bind", 123 | [{datatype, [string]}]}. 124 | 125 | %% - as_user (to bind as the authenticated user - requires a password) 126 | %% - anon (to bind anonymously) 127 | %% - {UserDN, Password} (to bind with a specified user name and password) 128 | %% 129 | %% Defaults to 'as_user'. 130 | 131 | {translation, "rabbitmq_auth_backend_ldap.dn_lookup_bind", 132 | fun(Conf) -> 133 | case cuttlefish:conf_get("auth_ldap.dn_lookup_bind", Conf, undefined) of 134 | as_user -> as_user; 135 | anon -> anon; 136 | _ -> 137 | User = cuttlefish:conf_get("auth_ldap.dn_lookup_bind.user_dn", Conf), 138 | Pass = cuttlefish:conf_get("auth_ldap.dn_lookup_bind.password", Conf), 139 | case {User, Pass} of 140 | {undefined, _} -> as_user; 141 | {_, undefined} -> as_user; 142 | _ -> {User, Pass} 143 | end 144 | end 145 | end}. 146 | 147 | %% Controls how to bind for authorisation queries and also to 148 | %% retrieve the details of users logging in without presenting a 149 | %% password (e.g., SASL EXTERNAL). 150 | %% One of 151 | %% - as_user (to bind as the authenticated user - requires a password) 152 | %% - anon (to bind anonymously) 153 | %% - {UserDN, Password} (to bind with a specified user name and password) 154 | %% 155 | %% Defaults to 'as_user'. 156 | 157 | {mapping, "auth_ldap.other_bind", "rabbitmq_auth_backend_ldap.other_bind", 158 | [{datatype, {enum, [as_user, anon]}}]}. 159 | 160 | {mapping, "auth_ldap.other_bind.user_dn", "rabbitmq_auth_backend_ldap.other_bind", 161 | [{datatype, string}]}. 162 | 163 | {mapping, "auth_ldap.other_bind.password", "rabbitmq_auth_backend_ldap.other_bind", 164 | [{datatype, string}]}. 165 | 166 | {translation, "rabbitmq_auth_backend_ldap.other_bind", 167 | fun(Conf) -> 168 | case cuttlefish:conf_get("auth_ldap.other_bind", Conf, undefined) of 169 | as_user -> as_user; 170 | anon -> anon; 171 | _ -> 172 | User = cuttlefish:conf_get("auth_ldap.other_bind.user_dn", Conf), 173 | Pass = cuttlefish:conf_get("auth_ldap.other_bind.password", Conf), 174 | case {User, Pass} of 175 | {undefined, _} -> as_user; 176 | {_, undefined} -> as_user; 177 | _ -> {User, Pass} 178 | end 179 | end 180 | end}. 181 | 182 | %% 183 | %% Authorisation 184 | %% ============= 185 | %% 186 | 187 | %% Groups are searched in the DN defined by the `group_lookup_base` 188 | %% configuration key, or the `dn_lookup_base` variable if 189 | %% former is `none`. 190 | 191 | {mapping, "auth_ldap.group_lookup_base", "rabbitmq_auth_backend_ldap.group_lookup_base", 192 | [{datatype, [{enum, [none]}, string]}]}. 193 | 194 | %% The LDAP plugin can perform a variety of queries against your 195 | %% LDAP server to determine questions of authorisation. See 196 | %% https://www.rabbitmq.com/ldap.html#authorisation for more 197 | %% information. 198 | 199 | %% Set the query to use when determining vhost access 200 | %% 201 | %% {vhost_access_query, {in_group, 202 | %% "ou=${vhost}-users,ou=vhosts,dc=example,dc=com"}}, 203 | 204 | %% Set the query to use when determining resource (e.g., queue) access 205 | %% 206 | %% {resource_access_query, {constant, true}}, 207 | 208 | %% Set queries to determine which tags a user has 209 | %% 210 | %% {tag_queries, []} 211 | % ]}, 212 | 213 | %% Connect to the LDAP server using TLS 214 | %% 215 | %% {use_ssl, false}, 216 | 217 | {mapping, "auth_ldap.use_ssl", "rabbitmq_auth_backend_ldap.use_ssl", 218 | [{datatype, {enum, [true, false]}}]}. 219 | 220 | %% Connect to the LDAP server using StartTLS 221 | %% 222 | %% {use_starttls, false}, 223 | 224 | {mapping, "auth_ldap.use_starttls", "rabbitmq_auth_backend_ldap.use_starttls", 225 | [{datatype, {enum, [true, false]}}]}. 226 | 227 | 228 | %% TLS options 229 | 230 | {mapping, "auth_ldap.ssl_options", "rabbitmq_auth_backend_ldap.ssl_options", [ 231 | {datatype, {enum, [none]}} 232 | ]}. 233 | 234 | {translation, "rabbitmq_auth_backend_ldap.ssl_options", 235 | fun(Conf) -> 236 | case cuttlefish:conf_get("auth_ldap.ssl_options", Conf, undefined) of 237 | none -> []; 238 | _ -> cuttlefish:invalid("Invalid auth_ldap.ssl_options") 239 | end 240 | end}. 241 | 242 | {mapping, "auth_ldap.ssl_options.verify", "rabbitmq_auth_backend_ldap.ssl_options.verify", [ 243 | {datatype, {enum, [verify_peer, verify_none]}}]}. 244 | 245 | {mapping, "auth_ldap.ssl_options.fail_if_no_peer_cert", "rabbitmq_auth_backend_ldap.ssl_options.fail_if_no_peer_cert", [ 246 | {datatype, {enum, [true, false]}}]}. 247 | 248 | {mapping, "auth_ldap.ssl_options.cacertfile", "rabbitmq_auth_backend_ldap.ssl_options.cacertfile", 249 | [{datatype, string}, {validators, ["file_accessible"]}]}. 250 | 251 | {mapping, "auth_ldap.ssl_options.certfile", "rabbitmq_auth_backend_ldap.ssl_options.certfile", 252 | [{datatype, string}, {validators, ["file_accessible"]}]}. 253 | 254 | {mapping, "auth_ldap.ssl_options.cacerts.$name", "rabbitmq_auth_backend_ldap.ssl_options.cacerts", 255 | [{datatype, string}]}. 256 | 257 | {translation, "rabbitmq_auth_backend_ldap.ssl_options.cacerts", 258 | fun(Conf) -> 259 | Settings = cuttlefish_variable:filter_by_prefix("auth_ldap.ssl_options.cacerts", Conf), 260 | [ list_to_binary(V) || {_, V} <- Settings ] 261 | end}. 262 | 263 | {mapping, "auth_ldap.ssl_options.cert", "rabbitmq_auth_backend_ldap.ssl_options.cert", 264 | [{datatype, string}]}. 265 | 266 | {translation, "rabbitmq_auth_backend_ldap.ssl_options.cert", 267 | fun(Conf) -> 268 | list_to_binary(cuttlefish:conf_get("auth_ldap.ssl_options.cert", Conf)) 269 | end}. 270 | 271 | {mapping, "auth_ldap.ssl_options.client_renegotiation", "rabbitmq_auth_backend_ldap.ssl_options.client_renegotiation", 272 | [{datatype, {enum, [true, false]}}]}. 273 | 274 | {mapping, "auth_ldap.ssl_options.crl_check", "rabbitmq_auth_backend_ldap.ssl_options.crl_check", 275 | [{datatype, [{enum, [true, false, peer, best_effort]}]}]}. 276 | 277 | {mapping, "auth_ldap.ssl_options.depth", "rabbitmq_auth_backend_ldap.ssl_options.depth", 278 | [{datatype, integer}, {validators, ["byte"]}]}. 279 | 280 | {mapping, "auth_ldap.ssl_options.dh", "rabbitmq_auth_backend_ldap.ssl_options.dh", 281 | [{datatype, string}]}. 282 | 283 | {translation, "rabbitmq_auth_backend_ldap.ssl_options.dh", 284 | fun(Conf) -> 285 | list_to_binary(cuttlefish:conf_get("auth_ldap.ssl_options.dh", Conf)) 286 | end}. 287 | 288 | {mapping, "auth_ldap.ssl_options.dhfile", "rabbitmq_auth_backend_ldap.ssl_options.dhfile", 289 | [{datatype, string}, {validators, ["file_accessible"]}]}. 290 | 291 | {mapping, "auth_ldap.ssl_options.honor_cipher_order", "rabbitmq_auth_backend_ldap.ssl_options.honor_cipher_order", 292 | [{datatype, {enum, [true, false]}}]}. 293 | 294 | {mapping, "auth_ldap.ssl_options.honor_ecc_order", "rabbitmq_auth_backend_ldap.ssl_options.honor_ecc_order", 295 | [{datatype, {enum, [true, false]}}]}. 296 | 297 | {mapping, "auth_ldap.ssl_options.key.RSAPrivateKey", "rabbitmq_auth_backend_ldap.ssl_options.key", 298 | [{datatype, string}]}. 299 | 300 | {mapping, "auth_ldap.ssl_options.key.DSAPrivateKey", "rabbitmq_auth_backend_ldap.ssl_options.key", 301 | [{datatype, string}]}. 302 | 303 | {mapping, "auth_ldap.ssl_options.key.PrivateKeyInfo", "rabbitmq_auth_backend_ldap.ssl_options.key", 304 | [{datatype, string}]}. 305 | 306 | {translation, "rabbitmq_auth_backend_ldap.ssl_options.key", 307 | fun(Conf) -> 308 | case cuttlefish_variable:filter_by_prefix("auth_ldap.ssl_options.key", Conf) of 309 | [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)}; 310 | _ -> undefined 311 | end 312 | end}. 313 | 314 | {mapping, "auth_ldap.ssl_options.keyfile", "rabbitmq_auth_backend_ldap.ssl_options.keyfile", 315 | [{datatype, string}, {validators, ["file_accessible"]}]}. 316 | 317 | {mapping, "auth_ldap.ssl_options.log_alert", "rabbitmq_auth_backend_ldap.ssl_options.log_alert", 318 | [{datatype, {enum, [true, false]}}]}. 319 | 320 | {mapping, "auth_ldap.ssl_options.password", "rabbitmq_auth_backend_ldap.ssl_options.password", 321 | [{datatype, string}]}. 322 | 323 | {mapping, "auth_ldap.ssl_options.psk_identity", "rabbitmq_auth_backend_ldap.ssl_options.psk_identity", 324 | [{datatype, string}]}. 325 | 326 | {mapping, "auth_ldap.ssl_options.reuse_sessions", "rabbitmq_auth_backend_ldap.ssl_options.reuse_sessions", 327 | [{datatype, {enum, [true, false]}}]}. 328 | 329 | {mapping, "auth_ldap.ssl_options.secure_renegotiate", "rabbitmq_auth_backend_ldap.ssl_options.secure_renegotiate", 330 | [{datatype, {enum, [true, false]}}]}. 331 | 332 | {mapping, "auth_ldap.ssl_options.versions.$version", "rabbitmq_auth_backend_ldap.ssl_options.versions", 333 | [{datatype, atom}]}. 334 | 335 | {translation, "rabbitmq_auth_backend_ldap.ssl_options.versions", 336 | fun(Conf) -> 337 | Settings = cuttlefish_variable:filter_by_prefix("auth_ldap.ssl_options.versions", Conf), 338 | [ V || {_, V} <- Settings ] 339 | end}. 340 | -------------------------------------------------------------------------------- /rabbitmq-components.mk: -------------------------------------------------------------------------------- 1 | ifeq ($(.DEFAULT_GOAL),) 2 | # Define default goal to `all` because this file defines some targets 3 | # before the inclusion of erlang.mk leading to the wrong target becoming 4 | # the default. 5 | .DEFAULT_GOAL = all 6 | endif 7 | 8 | # PROJECT_VERSION defaults to: 9 | # 1. the version exported by rabbitmq-server-release; 10 | # 2. the version stored in `git-revisions.txt`, if it exists; 11 | # 3. a version based on git-describe(1), if it is a Git clone; 12 | # 4. 0.0.0 13 | 14 | PROJECT_VERSION := $(RABBITMQ_VERSION) 15 | 16 | ifeq ($(PROJECT_VERSION),) 17 | PROJECT_VERSION := $(shell \ 18 | if test -f git-revisions.txt; then \ 19 | head -n1 git-revisions.txt | \ 20 | awk '{print $$$(words $(PROJECT_DESCRIPTION) version);}'; \ 21 | else \ 22 | (git describe --dirty --abbrev=7 --tags --always --first-parent \ 23 | 2>/dev/null || echo rabbitmq_v0_0_0) | \ 24 | sed -e 's/^rabbitmq_v//' -e 's/^v//' -e 's/_/./g' -e 's/-/+/' \ 25 | -e 's/-/./g'; \ 26 | fi) 27 | endif 28 | 29 | # -------------------------------------------------------------------- 30 | # RabbitMQ components. 31 | # -------------------------------------------------------------------- 32 | 33 | # For RabbitMQ repositories, we want to checkout branches which match 34 | # the parent project. For instance, if the parent project is on a 35 | # release tag, dependencies must be on the same release tag. If the 36 | # parent project is on a topic branch, dependencies must be on the same 37 | # topic branch or fallback to `stable` or `master` whichever was the 38 | # base of the topic branch. 39 | 40 | dep_amqp_client = git_rmq rabbitmq-erlang-client $(current_rmq_ref) $(base_rmq_ref) master 41 | dep_amqp10_client = git_rmq rabbitmq-amqp1.0-client $(current_rmq_ref) $(base_rmq_ref) master 42 | dep_amqp10_common = git_rmq rabbitmq-amqp1.0-common $(current_rmq_ref) $(base_rmq_ref) master 43 | dep_rabbit = git_rmq rabbitmq-server $(current_rmq_ref) $(base_rmq_ref) master 44 | dep_rabbit_common = git_rmq rabbitmq-common $(current_rmq_ref) $(base_rmq_ref) master 45 | dep_rabbitmq_amqp1_0 = git_rmq rabbitmq-amqp1.0 $(current_rmq_ref) $(base_rmq_ref) master 46 | dep_rabbitmq_auth_backend_amqp = git_rmq rabbitmq-auth-backend-amqp $(current_rmq_ref) $(base_rmq_ref) master 47 | dep_rabbitmq_auth_backend_cache = git_rmq rabbitmq-auth-backend-cache $(current_rmq_ref) $(base_rmq_ref) master 48 | dep_rabbitmq_auth_backend_http = git_rmq rabbitmq-auth-backend-http $(current_rmq_ref) $(base_rmq_ref) master 49 | dep_rabbitmq_auth_backend_ldap = git_rmq rabbitmq-auth-backend-ldap $(current_rmq_ref) $(base_rmq_ref) master 50 | dep_rabbitmq_auth_backend_oauth2 = git_rmq rabbitmq-auth-backend-oauth2 $(current_rmq_ref) $(base_rmq_ref) master 51 | dep_rabbitmq_auth_mechanism_ssl = git_rmq rabbitmq-auth-mechanism-ssl $(current_rmq_ref) $(base_rmq_ref) master 52 | dep_rabbitmq_aws = git_rmq rabbitmq-aws $(current_rmq_ref) $(base_rmq_ref) master 53 | dep_rabbitmq_boot_steps_visualiser = git_rmq rabbitmq-boot-steps-visualiser $(current_rmq_ref) $(base_rmq_ref) master 54 | dep_rabbitmq_cli = git_rmq rabbitmq-cli $(current_rmq_ref) $(base_rmq_ref) master 55 | dep_rabbitmq_codegen = git_rmq rabbitmq-codegen $(current_rmq_ref) $(base_rmq_ref) master 56 | dep_rabbitmq_consistent_hash_exchange = git_rmq rabbitmq-consistent-hash-exchange $(current_rmq_ref) $(base_rmq_ref) master 57 | dep_rabbitmq_ct_client_helpers = git_rmq rabbitmq-ct-client-helpers $(current_rmq_ref) $(base_rmq_ref) master 58 | dep_rabbitmq_ct_helpers = git_rmq rabbitmq-ct-helpers $(current_rmq_ref) $(base_rmq_ref) master 59 | dep_rabbitmq_delayed_message_exchange = git_rmq rabbitmq-delayed-message-exchange $(current_rmq_ref) $(base_rmq_ref) master 60 | dep_rabbitmq_dotnet_client = git_rmq rabbitmq-dotnet-client $(current_rmq_ref) $(base_rmq_ref) master 61 | dep_rabbitmq_event_exchange = git_rmq rabbitmq-event-exchange $(current_rmq_ref) $(base_rmq_ref) master 62 | dep_rabbitmq_federation = git_rmq rabbitmq-federation $(current_rmq_ref) $(base_rmq_ref) master 63 | dep_rabbitmq_federation_management = git_rmq rabbitmq-federation-management $(current_rmq_ref) $(base_rmq_ref) master 64 | dep_rabbitmq_java_client = git_rmq rabbitmq-java-client $(current_rmq_ref) $(base_rmq_ref) master 65 | dep_rabbitmq_jms_client = git_rmq rabbitmq-jms-client $(current_rmq_ref) $(base_rmq_ref) master 66 | dep_rabbitmq_jms_cts = git_rmq rabbitmq-jms-cts $(current_rmq_ref) $(base_rmq_ref) master 67 | dep_rabbitmq_jms_topic_exchange = git_rmq rabbitmq-jms-topic-exchange $(current_rmq_ref) $(base_rmq_ref) master 68 | dep_rabbitmq_lvc_exchange = git_rmq rabbitmq-lvc-exchange $(current_rmq_ref) $(base_rmq_ref) master 69 | dep_rabbitmq_management = git_rmq rabbitmq-management $(current_rmq_ref) $(base_rmq_ref) master 70 | dep_rabbitmq_management_agent = git_rmq rabbitmq-management-agent $(current_rmq_ref) $(base_rmq_ref) master 71 | dep_rabbitmq_management_exchange = git_rmq rabbitmq-management-exchange $(current_rmq_ref) $(base_rmq_ref) master 72 | dep_rabbitmq_management_themes = git_rmq rabbitmq-management-themes $(current_rmq_ref) $(base_rmq_ref) master 73 | dep_rabbitmq_message_timestamp = git_rmq rabbitmq-message-timestamp $(current_rmq_ref) $(base_rmq_ref) master 74 | dep_rabbitmq_metronome = git_rmq rabbitmq-metronome $(current_rmq_ref) $(base_rmq_ref) master 75 | dep_rabbitmq_mqtt = git_rmq rabbitmq-mqtt $(current_rmq_ref) $(base_rmq_ref) master 76 | dep_rabbitmq_objc_client = git_rmq rabbitmq-objc-client $(current_rmq_ref) $(base_rmq_ref) master 77 | dep_rabbitmq_peer_discovery_aws = git_rmq rabbitmq-peer-discovery-aws $(current_rmq_ref) $(base_rmq_ref) master 78 | dep_rabbitmq_peer_discovery_common = git_rmq rabbitmq-peer-discovery-common $(current_rmq_ref) $(base_rmq_ref) master 79 | dep_rabbitmq_peer_discovery_consul = git_rmq rabbitmq-peer-discovery-consul $(current_rmq_ref) $(base_rmq_ref) master 80 | dep_rabbitmq_peer_discovery_etcd = git_rmq rabbitmq-peer-discovery-etcd $(current_rmq_ref) $(base_rmq_ref) master 81 | dep_rabbitmq_peer_discovery_k8s = git_rmq rabbitmq-peer-discovery-k8s $(current_rmq_ref) $(base_rmq_ref) master 82 | dep_rabbitmq_prometheus = git_rmq rabbitmq-prometheus $(current_rmq_ref) $(base_rmq_ref) master 83 | dep_rabbitmq_random_exchange = git_rmq rabbitmq-random-exchange $(current_rmq_ref) $(base_rmq_ref) master 84 | dep_rabbitmq_recent_history_exchange = git_rmq rabbitmq-recent-history-exchange $(current_rmq_ref) $(base_rmq_ref) master 85 | dep_rabbitmq_routing_node_stamp = git_rmq rabbitmq-routing-node-stamp $(current_rmq_ref) $(base_rmq_ref) master 86 | dep_rabbitmq_rtopic_exchange = git_rmq rabbitmq-rtopic-exchange $(current_rmq_ref) $(base_rmq_ref) master 87 | dep_rabbitmq_server_release = git_rmq rabbitmq-server-release $(current_rmq_ref) $(base_rmq_ref) master 88 | dep_rabbitmq_sharding = git_rmq rabbitmq-sharding $(current_rmq_ref) $(base_rmq_ref) master 89 | dep_rabbitmq_shovel = git_rmq rabbitmq-shovel $(current_rmq_ref) $(base_rmq_ref) master 90 | dep_rabbitmq_shovel_management = git_rmq rabbitmq-shovel-management $(current_rmq_ref) $(base_rmq_ref) master 91 | dep_rabbitmq_stomp = git_rmq rabbitmq-stomp $(current_rmq_ref) $(base_rmq_ref) master 92 | dep_rabbitmq_stream = git_rmq rabbitmq-stream $(current_rmq_ref) $(base_rmq_ref) master 93 | dep_rabbitmq_toke = git_rmq rabbitmq-toke $(current_rmq_ref) $(base_rmq_ref) master 94 | dep_rabbitmq_top = git_rmq rabbitmq-top $(current_rmq_ref) $(base_rmq_ref) master 95 | dep_rabbitmq_tracing = git_rmq rabbitmq-tracing $(current_rmq_ref) $(base_rmq_ref) master 96 | dep_rabbitmq_trust_store = git_rmq rabbitmq-trust-store $(current_rmq_ref) $(base_rmq_ref) master 97 | dep_rabbitmq_test = git_rmq rabbitmq-test $(current_rmq_ref) $(base_rmq_ref) master 98 | dep_rabbitmq_web_dispatch = git_rmq rabbitmq-web-dispatch $(current_rmq_ref) $(base_rmq_ref) master 99 | dep_rabbitmq_web_stomp = git_rmq rabbitmq-web-stomp $(current_rmq_ref) $(base_rmq_ref) master 100 | dep_rabbitmq_web_stomp_examples = git_rmq rabbitmq-web-stomp-examples $(current_rmq_ref) $(base_rmq_ref) master 101 | dep_rabbitmq_web_mqtt = git_rmq rabbitmq-web-mqtt $(current_rmq_ref) $(base_rmq_ref) master 102 | dep_rabbitmq_web_mqtt_examples = git_rmq rabbitmq-web-mqtt-examples $(current_rmq_ref) $(base_rmq_ref) master 103 | dep_rabbitmq_website = git_rmq rabbitmq-website $(current_rmq_ref) $(base_rmq_ref) live master 104 | dep_toke = git_rmq toke $(current_rmq_ref) $(base_rmq_ref) master 105 | 106 | dep_rabbitmq_public_umbrella = git_rmq rabbitmq-public-umbrella $(current_rmq_ref) $(base_rmq_ref) master 107 | 108 | # Third-party dependencies version pinning. 109 | # 110 | # We do that in this file, which is copied in all projects, to ensure 111 | # all projects use the same versions. It avoids conflicts and makes it 112 | # possible to work with rabbitmq-public-umbrella. 113 | 114 | dep_accept = hex 0.3.5 115 | dep_cowboy = hex 2.8.0 116 | dep_cowlib = hex 2.9.1 117 | dep_jsx = hex 2.11.0 118 | dep_lager = hex 3.8.0 119 | dep_prometheus = git https://github.com/deadtrickster/prometheus.erl.git master 120 | dep_ra = git https://github.com/rabbitmq/ra.git master 121 | dep_ranch = hex 1.7.1 122 | dep_recon = hex 2.5.1 123 | dep_observer_cli = hex 1.5.4 124 | dep_stdout_formatter = hex 0.2.4 125 | dep_sysmon_handler = hex 1.3.0 126 | 127 | RABBITMQ_COMPONENTS = amqp_client \ 128 | amqp10_common \ 129 | amqp10_client \ 130 | rabbit \ 131 | rabbit_common \ 132 | rabbitmq_amqp1_0 \ 133 | rabbitmq_auth_backend_amqp \ 134 | rabbitmq_auth_backend_cache \ 135 | rabbitmq_auth_backend_http \ 136 | rabbitmq_auth_backend_ldap \ 137 | rabbitmq_auth_backend_oauth2 \ 138 | rabbitmq_auth_mechanism_ssl \ 139 | rabbitmq_aws \ 140 | rabbitmq_boot_steps_visualiser \ 141 | rabbitmq_cli \ 142 | rabbitmq_codegen \ 143 | rabbitmq_consistent_hash_exchange \ 144 | rabbitmq_ct_client_helpers \ 145 | rabbitmq_ct_helpers \ 146 | rabbitmq_delayed_message_exchange \ 147 | rabbitmq_dotnet_client \ 148 | rabbitmq_event_exchange \ 149 | rabbitmq_federation \ 150 | rabbitmq_federation_management \ 151 | rabbitmq_java_client \ 152 | rabbitmq_jms_client \ 153 | rabbitmq_jms_cts \ 154 | rabbitmq_jms_topic_exchange \ 155 | rabbitmq_lvc_exchange \ 156 | rabbitmq_management \ 157 | rabbitmq_management_agent \ 158 | rabbitmq_management_exchange \ 159 | rabbitmq_management_themes \ 160 | rabbitmq_message_timestamp \ 161 | rabbitmq_metronome \ 162 | rabbitmq_mqtt \ 163 | rabbitmq_objc_client \ 164 | rabbitmq_peer_discovery_aws \ 165 | rabbitmq_peer_discovery_common \ 166 | rabbitmq_peer_discovery_consul \ 167 | rabbitmq_peer_discovery_etcd \ 168 | rabbitmq_peer_discovery_k8s \ 169 | rabbitmq_prometheus \ 170 | rabbitmq_random_exchange \ 171 | rabbitmq_recent_history_exchange \ 172 | rabbitmq_routing_node_stamp \ 173 | rabbitmq_rtopic_exchange \ 174 | rabbitmq_server_release \ 175 | rabbitmq_sharding \ 176 | rabbitmq_shovel \ 177 | rabbitmq_shovel_management \ 178 | rabbitmq_stomp \ 179 | rabbitmq_stream \ 180 | rabbitmq_toke \ 181 | rabbitmq_top \ 182 | rabbitmq_tracing \ 183 | rabbitmq_trust_store \ 184 | rabbitmq_web_dispatch \ 185 | rabbitmq_web_mqtt \ 186 | rabbitmq_web_mqtt_examples \ 187 | rabbitmq_web_stomp \ 188 | rabbitmq_web_stomp_examples \ 189 | rabbitmq_website 190 | 191 | # Erlang.mk does not rebuild dependencies by default, once they were 192 | # compiled once, except for those listed in the `$(FORCE_REBUILD)` 193 | # variable. 194 | # 195 | # We want all RabbitMQ components to always be rebuilt: this eases 196 | # the work on several components at the same time. 197 | 198 | FORCE_REBUILD = $(RABBITMQ_COMPONENTS) 199 | 200 | # Several components have a custom erlang.mk/build.config, mainly 201 | # to disable eunit. Therefore, we can't use the top-level project's 202 | # erlang.mk copy. 203 | NO_AUTOPATCH += $(RABBITMQ_COMPONENTS) 204 | 205 | ifeq ($(origin current_rmq_ref),undefined) 206 | ifneq ($(wildcard .git),) 207 | current_rmq_ref := $(shell (\ 208 | ref=$$(LANG=C git branch --list | awk '/^\* \(.*detached / {ref=$$0; sub(/.*detached [^ ]+ /, "", ref); sub(/\)$$/, "", ref); print ref; exit;} /^\* / {ref=$$0; sub(/^\* /, "", ref); print ref; exit}');\ 209 | if test "$$(git rev-parse --short HEAD)" != "$$ref"; then echo "$$ref"; fi)) 210 | else 211 | current_rmq_ref := master 212 | endif 213 | endif 214 | export current_rmq_ref 215 | 216 | ifeq ($(origin base_rmq_ref),undefined) 217 | ifneq ($(wildcard .git),) 218 | possible_base_rmq_ref := master 219 | ifeq ($(possible_base_rmq_ref),$(current_rmq_ref)) 220 | base_rmq_ref := $(current_rmq_ref) 221 | else 222 | base_rmq_ref := $(shell \ 223 | (git rev-parse --verify -q master >/dev/null && \ 224 | git rev-parse --verify -q $(possible_base_rmq_ref) >/dev/null && \ 225 | git merge-base --is-ancestor $$(git merge-base master HEAD) $(possible_base_rmq_ref) && \ 226 | echo $(possible_base_rmq_ref)) || \ 227 | echo master) 228 | endif 229 | else 230 | base_rmq_ref := master 231 | endif 232 | endif 233 | export base_rmq_ref 234 | 235 | # Repository URL selection. 236 | # 237 | # First, we infer other components' location from the current project 238 | # repository URL, if it's a Git repository: 239 | # - We take the "origin" remote URL as the base 240 | # - The current project name and repository name is replaced by the 241 | # target's properties: 242 | # eg. rabbitmq-common is replaced by rabbitmq-codegen 243 | # eg. rabbit_common is replaced by rabbitmq_codegen 244 | # 245 | # If cloning from this computed location fails, we fallback to RabbitMQ 246 | # upstream which is GitHub. 247 | 248 | # Macro to transform eg. "rabbit_common" to "rabbitmq-common". 249 | rmq_cmp_repo_name = $(word 2,$(dep_$(1))) 250 | 251 | # Upstream URL for the current project. 252 | RABBITMQ_COMPONENT_REPO_NAME := $(call rmq_cmp_repo_name,$(PROJECT)) 253 | RABBITMQ_UPSTREAM_FETCH_URL ?= https://github.com/rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 254 | RABBITMQ_UPSTREAM_PUSH_URL ?= git@github.com:rabbitmq/$(RABBITMQ_COMPONENT_REPO_NAME).git 255 | 256 | # Current URL for the current project. If this is not a Git clone, 257 | # default to the upstream Git repository. 258 | ifneq ($(wildcard .git),) 259 | git_origin_fetch_url := $(shell git config remote.origin.url) 260 | git_origin_push_url := $(shell git config remote.origin.pushurl || git config remote.origin.url) 261 | RABBITMQ_CURRENT_FETCH_URL ?= $(git_origin_fetch_url) 262 | RABBITMQ_CURRENT_PUSH_URL ?= $(git_origin_push_url) 263 | else 264 | RABBITMQ_CURRENT_FETCH_URL ?= $(RABBITMQ_UPSTREAM_FETCH_URL) 265 | RABBITMQ_CURRENT_PUSH_URL ?= $(RABBITMQ_UPSTREAM_PUSH_URL) 266 | endif 267 | 268 | # Macro to replace the following pattern: 269 | # 1. /foo.git -> /bar.git 270 | # 2. /foo -> /bar 271 | # 3. /foo/ -> /bar/ 272 | subst_repo_name = $(patsubst %/$(1)/%,%/$(2)/%,$(patsubst %/$(1),%/$(2),$(patsubst %/$(1).git,%/$(2).git,$(3)))) 273 | 274 | # Macro to replace both the project's name (eg. "rabbit_common") and 275 | # repository name (eg. "rabbitmq-common") by the target's equivalent. 276 | # 277 | # This macro is kept on one line because we don't want whitespaces in 278 | # the returned value, as it's used in $(dep_fetch_git_rmq) in a shell 279 | # single-quoted string. 280 | dep_rmq_repo = $(if $(dep_$(2)),$(call subst_repo_name,$(PROJECT),$(2),$(call subst_repo_name,$(RABBITMQ_COMPONENT_REPO_NAME),$(call rmq_cmp_repo_name,$(2)),$(1))),$(pkg_$(1)_repo)) 281 | 282 | dep_rmq_commits = $(if $(dep_$(1)), \ 283 | $(wordlist 3,$(words $(dep_$(1))),$(dep_$(1))), \ 284 | $(pkg_$(1)_commit)) 285 | 286 | define dep_fetch_git_rmq 287 | fetch_url1='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_FETCH_URL),$(1))'; \ 288 | fetch_url2='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_FETCH_URL),$(1))'; \ 289 | if test "$$$$fetch_url1" != '$(RABBITMQ_CURRENT_FETCH_URL)' && \ 290 | git clone -q -n -- "$$$$fetch_url1" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 291 | fetch_url="$$$$fetch_url1"; \ 292 | push_url='$(call dep_rmq_repo,$(RABBITMQ_CURRENT_PUSH_URL),$(1))'; \ 293 | elif git clone -q -n -- "$$$$fetch_url2" $(DEPS_DIR)/$(call dep_name,$(1)); then \ 294 | fetch_url="$$$$fetch_url2"; \ 295 | push_url='$(call dep_rmq_repo,$(RABBITMQ_UPSTREAM_PUSH_URL),$(1))'; \ 296 | fi; \ 297 | cd $(DEPS_DIR)/$(call dep_name,$(1)) && ( \ 298 | $(foreach ref,$(call dep_rmq_commits,$(1)), \ 299 | git checkout -q $(ref) >/dev/null 2>&1 || \ 300 | ) \ 301 | (echo "error: no valid pathspec among: $(call dep_rmq_commits,$(1))" \ 302 | 1>&2 && false) ) && \ 303 | (test "$$$$fetch_url" = "$$$$push_url" || \ 304 | git remote set-url --push origin "$$$$push_url") 305 | endef 306 | 307 | # -------------------------------------------------------------------- 308 | # Component distribution. 309 | # -------------------------------------------------------------------- 310 | 311 | list-dist-deps:: 312 | @: 313 | 314 | prepare-dist:: 315 | @: 316 | 317 | # -------------------------------------------------------------------- 318 | # Umbrella-specific settings. 319 | # -------------------------------------------------------------------- 320 | 321 | # If the top-level project is a RabbitMQ component, we override 322 | # $(DEPS_DIR) for this project to point to the top-level's one. 323 | # 324 | # We also verify that the guessed DEPS_DIR is actually named `deps`, 325 | # to rule out any situation where it is a coincidence that we found a 326 | # `rabbitmq-components.mk` up upper directories. 327 | 328 | possible_deps_dir_1 = $(abspath ..) 329 | possible_deps_dir_2 = $(abspath ../../..) 330 | 331 | ifeq ($(notdir $(possible_deps_dir_1)),deps) 332 | ifneq ($(wildcard $(possible_deps_dir_1)/../rabbitmq-components.mk),) 333 | deps_dir_overriden = 1 334 | DEPS_DIR ?= $(possible_deps_dir_1) 335 | DISABLE_DISTCLEAN = 1 336 | endif 337 | endif 338 | 339 | ifeq ($(deps_dir_overriden),) 340 | ifeq ($(notdir $(possible_deps_dir_2)),deps) 341 | ifneq ($(wildcard $(possible_deps_dir_2)/../rabbitmq-components.mk),) 342 | deps_dir_overriden = 1 343 | DEPS_DIR ?= $(possible_deps_dir_2) 344 | DISABLE_DISTCLEAN = 1 345 | endif 346 | endif 347 | endif 348 | 349 | ifneq ($(wildcard UMBRELLA.md),) 350 | DISABLE_DISTCLEAN = 1 351 | endif 352 | 353 | # We disable `make distclean` so $(DEPS_DIR) is not accidentally removed. 354 | 355 | ifeq ($(DISABLE_DISTCLEAN),1) 356 | ifneq ($(filter distclean distclean-deps,$(MAKECMDGOALS)),) 357 | SKIP_DEPS = 1 358 | endif 359 | endif 360 | -------------------------------------------------------------------------------- /src/rabbit_auth_backend_ldap.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_auth_backend_ldap). 9 | 10 | %% Connect to an LDAP server for authentication and authorisation 11 | 12 | -include_lib("eldap/include/eldap.hrl"). 13 | -include_lib("rabbit_common/include/rabbit.hrl"). 14 | 15 | -behaviour(rabbit_authn_backend). 16 | -behaviour(rabbit_authz_backend). 17 | 18 | -export([user_login_authentication/2, user_login_authorization/2, 19 | check_vhost_access/3, check_resource_access/4, check_topic_access/4, 20 | state_can_expire/0]). 21 | 22 | -export([get_connections/0]). 23 | 24 | %% for tests 25 | -export([purge_connections/0]). 26 | 27 | -define(L(F, A), log("LDAP " ++ F, A)). 28 | -define(L1(F, A), log(" LDAP " ++ F, A)). 29 | -define(L2(F, A), log(" LDAP " ++ F, A)). 30 | -define(SCRUBBED_CREDENTIAL, "xxxx"). 31 | -define(RESOURCE_ACCESS_QUERY_VARIABLES, [username, user_dn, vhost, resource, name, permission]). 32 | 33 | -define(LDAP_OPERATION_RETRIES, 10). 34 | 35 | -import(rabbit_misc, [pget/2]). 36 | 37 | -record(impl, { user_dn, password }). 38 | 39 | %%-------------------------------------------------------------------- 40 | 41 | get_connections() -> 42 | worker_pool:submit(ldap_pool, fun() -> get(ldap_conns) end, reuse). 43 | 44 | purge_connections() -> 45 | [ok = worker_pool:submit(ldap_pool, 46 | fun() -> purge_conn(Anon, Servers, Opts) end, reuse) 47 | || {{Anon, Servers, Opts}, _} <- maps:to_list(get_connections())], 48 | ok. 49 | 50 | user_login_authentication(Username, []) -> 51 | %% Without password, e.g. EXTERNAL 52 | ?L("CHECK: passwordless login for ~s", [Username]), 53 | R = with_ldap(creds(none), 54 | fun(LDAP) -> do_login(Username, unknown, none, LDAP) end), 55 | ?L("DECISION: passwordless login for ~s: ~p", 56 | [Username, log_result(R)]), 57 | R; 58 | 59 | user_login_authentication(Username, AuthProps) when is_list(AuthProps) -> 60 | case pget(password, AuthProps) of 61 | undefined -> user_login_authentication(Username, []); 62 | <<>> -> 63 | %% Password "" is special in LDAP, see 64 | %% https://tools.ietf.org/html/rfc4513#section-5.1.2 65 | ?L("CHECK: unauthenticated login for ~s", [Username]), 66 | ?L("DECISION: unauthenticated login for ~s: denied", [Username]), 67 | {refused, "user '~s' - unauthenticated bind not allowed", [Username]}; 68 | PW -> 69 | ?L("CHECK: login for ~s", [Username]), 70 | R = case dn_lookup_when() of 71 | prebind -> UserDN = username_to_dn_prebind(Username), 72 | with_ldap({ok, {UserDN, PW}}, 73 | login_fun(Username, UserDN, PW, AuthProps)); 74 | _ -> with_ldap({ok, {simple_bind_fill_pattern(Username), PW}}, 75 | login_fun(Username, unknown, PW, AuthProps)) 76 | end, 77 | ?L("DECISION: login for ~s: ~p", [Username, log_result(R)]), 78 | R 79 | end; 80 | 81 | user_login_authentication(Username, AuthProps) -> 82 | exit({unknown_auth_props, Username, AuthProps}). 83 | 84 | user_login_authorization(Username, AuthProps) -> 85 | case user_login_authentication(Username, AuthProps) of 86 | {ok, #auth_user{impl = Impl, tags = Tags}} -> {ok, Impl, Tags}; 87 | Else -> Else 88 | end. 89 | 90 | check_vhost_access(User = #auth_user{username = Username, 91 | impl = #impl{user_dn = UserDN}}, 92 | VHost, AuthzData) -> 93 | OptionsArgs = context_as_options(AuthzData, undefined), 94 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 95 | Args = [{username, Username}, 96 | {user_dn, UserDN}, 97 | {vhost, VHost}] ++ OptionsArgs ++ ADArgs, 98 | ?L("CHECK: ~s for ~s", [log_vhost(Args), log_user(User)]), 99 | R0 = evaluate_ldap(env(vhost_access_query), Args, User), 100 | R1 = ensure_rabbit_authz_backend_result(R0), 101 | ?L("DECISION: ~s for ~s: ~p (~p)", 102 | [log_vhost(Args), log_user(User), 103 | log_result(R0), log_result(R1)]), 104 | R1. 105 | 106 | check_resource_access(User = #auth_user{username = Username, 107 | impl = #impl{user_dn = UserDN}}, 108 | #resource{virtual_host = VHost, kind = Type, name = Name}, 109 | Permission, 110 | AuthzContext) -> 111 | OptionsArgs = context_as_options(AuthzContext, undefined), 112 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 113 | Args = [{username, Username}, 114 | {user_dn, UserDN}, 115 | {vhost, VHost}, 116 | {resource, Type}, 117 | {name, Name}, 118 | {permission, Permission}] ++ OptionsArgs ++ ADArgs, 119 | ?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]), 120 | R0 = evaluate_ldap(env(resource_access_query), Args, User), 121 | R1 = ensure_rabbit_authz_backend_result(R0), 122 | ?L("DECISION: ~s for ~s: ~p (~p)", 123 | [log_resource(Args), log_user(User), 124 | log_result(R0), log_result(R1)]), 125 | R1. 126 | 127 | check_topic_access(User = #auth_user{username = Username, 128 | impl = #impl{user_dn = UserDN}}, 129 | #resource{virtual_host = VHost, kind = topic = Resource, name = Name}, 130 | Permission, 131 | Context) -> 132 | OptionsArgs = context_as_options(Context, undefined), 133 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 134 | Args = [{username, Username}, 135 | {user_dn, UserDN}, 136 | {vhost, VHost}, 137 | {resource, Resource}, 138 | {name, Name}, 139 | {permission, Permission}] ++ OptionsArgs ++ ADArgs, 140 | ?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]), 141 | R0 = evaluate_ldap(env(topic_access_query), Args, User), 142 | R1 = ensure_rabbit_authz_backend_result(R0), 143 | ?L("DECISION: ~s for ~s: ~p (~p)", 144 | [log_resource(Args), log_user(User), 145 | log_result(R0), log_result(R1)]), 146 | R1. 147 | 148 | state_can_expire() -> false. 149 | 150 | %%-------------------------------------------------------------------- 151 | 152 | ensure_rabbit_authz_backend_result(true) -> 153 | true; 154 | ensure_rabbit_authz_backend_result(false) -> 155 | false; 156 | ensure_rabbit_authz_backend_result({error, _}=Error) -> 157 | Error; 158 | % rabbitmq/rabbitmq-auth-backend-ldap#116 159 | ensure_rabbit_authz_backend_result({refused, _, _}) -> 160 | false; 161 | ensure_rabbit_authz_backend_result({ok, _}) -> 162 | true; 163 | ensure_rabbit_authz_backend_result({ok, _, _}) -> 164 | true. 165 | 166 | context_as_options(Context, Namespace) when is_map(Context) -> 167 | % filter keys that would erase fixed variables 168 | lists:flatten([begin 169 | Value = maps:get(Key, Context), 170 | case Value of 171 | MapOfValues when is_map(MapOfValues) -> 172 | context_as_options(MapOfValues, Key); 173 | SimpleValue -> 174 | case Namespace of 175 | undefined -> 176 | {rabbit_data_coercion:to_atom(Key), SimpleValue}; 177 | Namespace -> 178 | {create_option_name_with_namespace(Namespace, Key), Value} 179 | end 180 | end 181 | end || Key <- maps:keys(Context), lists:member( 182 | rabbit_data_coercion:to_atom(Key), 183 | ?RESOURCE_ACCESS_QUERY_VARIABLES) =:= false]); 184 | context_as_options(_, _) -> 185 | []. 186 | 187 | create_option_name_with_namespace(Namespace, Key) -> 188 | rabbit_data_coercion:to_atom( 189 | rabbit_data_coercion:to_list(Namespace) ++ "." ++ rabbit_data_coercion:to_list(Key) 190 | ). 191 | 192 | evaluate(Query, Args, User, LDAP) -> 193 | ?L1("evaluating query: ~p", [Query]), 194 | evaluate0(Query, Args, User, LDAP). 195 | 196 | evaluate0({constant, Bool}, _Args, _User, _LDAP) -> 197 | ?L1("evaluated constant: ~p", [Bool]), 198 | Bool; 199 | 200 | evaluate0({for, [{Type, Value, SubQuery}|Rest]}, Args, User, LDAP) -> 201 | case pget(Type, Args) of 202 | undefined -> {error, {args_do_not_contain, Type, Args}}; 203 | Value -> ?L1("selecting subquery ~s = ~s", [Type, Value]), 204 | evaluate(SubQuery, Args, User, LDAP); 205 | _ -> evaluate0({for, Rest}, Args, User, LDAP) 206 | end; 207 | 208 | evaluate0({for, []}, _Args, _User, _LDAP) -> 209 | {error, {for_query_incomplete}}; 210 | 211 | evaluate0({exists, DNPattern}, Args, _User, LDAP) -> 212 | %% eldap forces us to have a filter. objectClass should always be there. 213 | Filter = eldap:present("objectClass"), 214 | DN = fill(DNPattern, Args), 215 | R = object_exists(DN, Filter, LDAP), 216 | ?L1("evaluated exists for \"~s\": ~p", [DN, R]), 217 | R; 218 | 219 | evaluate0({in_group, DNPattern}, Args, User, LDAP) -> 220 | evaluate({in_group, DNPattern, "member"}, Args, User, LDAP); 221 | 222 | evaluate0({in_group, DNPattern, Desc}, Args, 223 | #auth_user{impl = #impl{user_dn = UserDN}}, LDAP) -> 224 | Filter = eldap:equalityMatch(Desc, UserDN), 225 | DN = fill(DNPattern, Args), 226 | R = object_exists(DN, Filter, LDAP), 227 | ?L1("evaluated in_group for \"~s\": ~p", [DN, R]), 228 | R; 229 | 230 | evaluate0({in_group_nested, DNPattern}, Args, User, LDAP) -> 231 | evaluate({in_group_nested, DNPattern, "member", subtree}, 232 | Args, User, LDAP); 233 | evaluate0({in_group_nested, DNPattern, Desc}, Args, User, LDAP) -> 234 | evaluate({in_group_nested, DNPattern, Desc, subtree}, 235 | Args, User, LDAP); 236 | evaluate0({in_group_nested, DNPattern, Desc, Scope}, Args, 237 | #auth_user{impl = #impl{user_dn = UserDN}}, LDAP) -> 238 | GroupsBase = case env(group_lookup_base) of 239 | none -> 240 | get_expected_env_str(dn_lookup_base, none); 241 | B -> 242 | B 243 | end, 244 | GroupDN = fill(DNPattern, Args), 245 | EldapScope = 246 | case Scope of 247 | subtree -> eldap:wholeSubtree(); 248 | singlelevel -> eldap:singleLevel(); 249 | single_level -> eldap:singleLevel(); 250 | onelevel -> eldap:singleLevel(); 251 | one_level -> eldap:singleLevel() 252 | end, 253 | search_nested_group(LDAP, Desc, GroupsBase, EldapScope, UserDN, GroupDN, []); 254 | 255 | evaluate0({'not', SubQuery}, Args, User, LDAP) -> 256 | R = evaluate(SubQuery, Args, User, LDAP), 257 | ?L1("negated result to ~s", [R]), 258 | not R; 259 | 260 | evaluate0({'and', Queries}, Args, User, LDAP) when is_list(Queries) -> 261 | R = lists:foldl(fun (Q, true) -> evaluate(Q, Args, User, LDAP); 262 | % Treat any non-true result as false 263 | (_Q, _Result) -> false 264 | end, true, Queries), 265 | ?L1("'and' result: ~s", [R]), 266 | R; 267 | 268 | evaluate0({'or', Queries}, Args, User, LDAP) when is_list(Queries) -> 269 | R = lists:foldl(fun (_Q, true) -> true; 270 | % Treat any non-true result as false 271 | (Q, _Result) -> evaluate(Q, Args, User, LDAP) 272 | end, false, Queries), 273 | ?L1("'or' result: ~s", [R]), 274 | R; 275 | 276 | evaluate0({equals, StringQuery1, StringQuery2}, Args, User, LDAP) -> 277 | safe_eval(fun (String1, String2) -> 278 | R = if String1 =:= String2 -> true; 279 | true -> is_multi_attr_member(String1, String2) 280 | end, 281 | ?L1("evaluated equals \"~s\", \"~s\": ~s", 282 | [format_multi_attr(String1), 283 | format_multi_attr(String2), R]), 284 | R 285 | end, 286 | evaluate(StringQuery1, Args, User, LDAP), 287 | evaluate(StringQuery2, Args, User, LDAP)); 288 | 289 | evaluate0({match, {string, _} = StringQuery, {string, _} = REQuery}, Args, User, LDAP) -> 290 | safe_eval(fun (String1, String2) -> 291 | do_match(String1, String2) 292 | end, 293 | evaluate(StringQuery, Args, User, LDAP), 294 | evaluate(REQuery, Args, User, LDAP)); 295 | 296 | evaluate0({match, StringQuery, {string, _} = REQuery}, Args, User, LDAP) when is_list(StringQuery)-> 297 | safe_eval(fun (String1, String2) -> 298 | do_match(String1, String2) 299 | end, 300 | evaluate(StringQuery, Args, User, LDAP), 301 | evaluate(REQuery, Args, User, LDAP)); 302 | 303 | evaluate0({match, {string, _} = StringQuery, REQuery}, Args, User, LDAP) when is_list(REQuery) -> 304 | safe_eval(fun (String1, String2) -> 305 | do_match(String1, String2) 306 | end, 307 | evaluate(StringQuery, Args, User, LDAP), 308 | evaluate(REQuery, Args, User, LDAP)); 309 | 310 | evaluate0({match, StringQuery, REQuery}, Args, User, LDAP) when is_list(StringQuery), 311 | is_list(REQuery) -> 312 | safe_eval(fun (String1, String2) -> 313 | do_match(String1, String2) 314 | end, 315 | evaluate(StringQuery, Args, User, LDAP), 316 | evaluate(REQuery, Args, User, LDAP)); 317 | 318 | evaluate0({match, StringQuery, REQuery}, Args, User, LDAP) -> 319 | safe_eval(fun (String1, String2) -> 320 | do_match_multi(String1, String2) 321 | end, 322 | evaluate(StringQuery, Args, User, LDAP), 323 | evaluate(REQuery, Args, User, LDAP)); 324 | 325 | evaluate0(StringPattern, Args, User, LDAP) when is_list(StringPattern) -> 326 | evaluate0({string, StringPattern}, Args, User, LDAP); 327 | 328 | evaluate0({string, StringPattern}, Args, _User, _LDAP) -> 329 | R = fill(StringPattern, Args), 330 | ?L1("evaluated string for \"~s\"", [R]), 331 | R; 332 | 333 | evaluate0({attribute, DNPattern, AttributeName}, Args, _User, LDAP) -> 334 | DN = fill(DNPattern, Args), 335 | R = attribute(DN, AttributeName, LDAP), 336 | ?L1("evaluated attribute \"~s\" for \"~s\": ~p", 337 | [AttributeName, DN, format_multi_attr(R)]), 338 | R; 339 | 340 | evaluate0(Q, Args, _User, _LDAP) -> 341 | {error, {unrecognised_query, Q, Args}}. 342 | 343 | search_groups(LDAP, Desc, GroupsBase, Scope, DN) -> 344 | Filter = eldap:equalityMatch(Desc, DN), 345 | case eldap:search(LDAP, 346 | [{base, GroupsBase}, 347 | {filter, Filter}, 348 | {attributes, ["dn"]}, 349 | {scope, Scope}]) of 350 | {error, _} = E -> 351 | ?L("error searching for parent groups for \"~s\": ~p", [DN, E]), 352 | []; 353 | {ok, {referral, Referrals}} -> 354 | {error, {referrals_not_supported, Referrals}}; 355 | {ok, #eldap_search_result{entries = []}} -> 356 | []; 357 | {ok, #eldap_search_result{entries = Entries}} -> 358 | [ON || #eldap_entry{object_name = ON} <- Entries] 359 | end. 360 | 361 | search_nested_group(LDAP, Desc, GroupsBase, Scope, CurrentDN, TargetDN, Path) -> 362 | case lists:member(CurrentDN, Path) of 363 | true -> 364 | ?L("recursive cycle on DN ~s while searching for group ~s", 365 | [CurrentDN, TargetDN]), 366 | false; 367 | false -> 368 | GroupDNs = search_groups(LDAP, Desc, GroupsBase, Scope, CurrentDN), 369 | case lists:member(TargetDN, GroupDNs) of 370 | true -> 371 | true; 372 | false -> 373 | NextPath = [CurrentDN | Path], 374 | lists:any(fun(DN) -> 375 | search_nested_group(LDAP, Desc, GroupsBase, Scope, 376 | DN, TargetDN, NextPath) 377 | end, 378 | GroupDNs) 379 | end 380 | end. 381 | 382 | safe_eval(_F, {error, _}, _) -> false; 383 | safe_eval(_F, _, {error, _}) -> false; 384 | safe_eval(F, V1, V2) -> F(V1, V2). 385 | 386 | do_match(S1, S2) -> 387 | case re:run(S1, S2) of 388 | {match, _} -> 389 | log_match(S1, S2, R = true), 390 | R; 391 | nomatch -> 392 | log_match(S1, S2, R = false), 393 | R 394 | end. 395 | 396 | %% In some cases when fetching regular expressions, LDAP evalution() 397 | %% returns a list of strings, so we need to wrap guards around that. 398 | %% If a list of strings is returned, loop and match versus each element. 399 | do_match_multi(S1, []) -> 400 | log_match(S1, [], R = false), 401 | R; 402 | do_match_multi(S1 = [H1|_], [H2|Tail]) when is_list(H2) and not is_list(H1) -> 403 | case re:run(S1, H2) of 404 | {match, _} -> 405 | log_match(S1, H2, R = true), 406 | R; 407 | _ -> 408 | log_match(S1,H2, false), 409 | do_match_multi(S1, Tail) 410 | end; 411 | do_match_multi([], S2) -> 412 | log_match([], S2, R = false), 413 | R; 414 | do_match_multi([H1|Tail], S2 = [H2|_] ) when is_list(H1) and not is_list(H2) -> 415 | case re:run(H1, S2) of 416 | {match, _} -> 417 | log_match(H1, S2, R = true), 418 | R; 419 | _ -> 420 | log_match(H1, S2, false), 421 | do_match_multi(Tail, S2) 422 | end; 423 | do_match_multi([H1|_],[H2|_]) when is_list(H1) and is_list(H2) -> 424 | false; %% Unsupported combination 425 | do_match_multi(S1, S2) -> 426 | do_match(S1, S2). 427 | 428 | log_match(String, RE, Result) -> 429 | ?L1("evaluated match \"~s\" against RE \"~s\": ~s", 430 | [format_multi_attr(String), 431 | format_multi_attr(RE), Result]). 432 | 433 | object_exists(DN, Filter, LDAP) -> 434 | case eldap:search(LDAP, 435 | [{base, DN}, 436 | {filter, Filter}, 437 | {attributes, ["objectClass"]}, %% Reduce verbiage 438 | {scope, eldap:baseObject()}]) of 439 | {ok, {referral, Referrals}} -> 440 | {error, {referrals_not_supported, Referrals}}; 441 | {ok, #eldap_search_result{entries = Entries}} -> 442 | length(Entries) > 0; 443 | {error, _} = E -> 444 | E 445 | end. 446 | 447 | attribute(DN, AttributeName, LDAP) -> 448 | case eldap:search(LDAP, 449 | [{base, DN}, 450 | {filter, eldap:present("objectClass")}, 451 | {attributes, [AttributeName]}]) of 452 | {ok, {referral, Referrals}} -> 453 | {error, {referrals_not_supported, Referrals}}; 454 | {ok, #eldap_search_result{entries = E = [#eldap_entry{}|_]}} -> 455 | get_attributes(AttributeName, E); 456 | {ok, #eldap_search_result{entries = _}} -> 457 | {error, not_found}; 458 | {error, _} = E -> 459 | E 460 | end. 461 | 462 | evaluate_ldap(Q, Args, User) -> 463 | with_ldap(creds(User), fun(LDAP) -> evaluate(Q, Args, User, LDAP) end). 464 | 465 | %%-------------------------------------------------------------------- 466 | 467 | with_ldap(Creds, Fun) -> with_ldap(Creds, Fun, env(servers)). 468 | 469 | with_ldap(_Creds, _Fun, undefined) -> 470 | {error, no_ldap_servers_defined}; 471 | 472 | with_ldap({error, _} = E, _Fun, _State) -> 473 | E; 474 | 475 | %% TODO - while we now pool LDAP connections we don't make any attempt 476 | %% to avoid rebinding if the connection is already bound as the user 477 | %% of interest, so this could still be more efficient. 478 | with_ldap({ok, Creds}, Fun, Servers) -> 479 | Opts0 = [{port, env(port)}, 480 | {idle_timeout, env(idle_timeout)}, 481 | {anon_auth, env(anon_auth)}], 482 | Opts1 = case env(log) of 483 | network -> 484 | Pre = " LDAP network traffic: ", 485 | rabbit_log_ldap:info( 486 | " LDAP connecting to servers: ~p~n", [Servers]), 487 | [{log, fun(1, S, A) -> rabbit_log_ldap:warning(Pre ++ S, A); 488 | (2, S, A) -> 489 | rabbit_log_ldap:info(Pre ++ S, scrub_creds(A, [])) 490 | end} | Opts0]; 491 | network_unsafe -> 492 | Pre = " LDAP network traffic: ", 493 | rabbit_log_ldap:info( 494 | " LDAP connecting to servers: ~p~n", [Servers]), 495 | [{log, fun(1, S, A) -> rabbit_log_ldap:warning(Pre ++ S, A); 496 | (2, S, A) -> rabbit_log_ldap:info( Pre ++ S, A) 497 | end} | Opts0]; 498 | _ -> 499 | Opts0 500 | end, 501 | %% eldap defaults to 'infinity' but doesn't allow you to set that. Harrumph. 502 | Opts = case env(timeout) of 503 | infinity -> Opts1; 504 | MS -> [{timeout, MS} | Opts1] 505 | end, 506 | 507 | worker_pool:submit( 508 | ldap_pool, 509 | fun () -> 510 | case with_login(Creds, Servers, Opts, Fun) of 511 | {error, {gen_tcp_error, _}} -> 512 | purge_connection(Creds, Servers, Opts), 513 | with_login(Creds, Servers, Opts, Fun); 514 | Result -> Result 515 | end 516 | end, reuse). 517 | 518 | with_login(Creds, Servers, Opts, Fun) -> 519 | with_login(Creds, Servers, Opts, Fun, ?LDAP_OPERATION_RETRIES). 520 | with_login(_Creds, _Servers, _Opts, _Fun, 0 = _RetriesLeft) -> 521 | rabbit_log_ldap:warning("LDAP failed to perform an operation. TCP connection to a LDAP server was closed or otherwise defunct. Exhausted all retries."), 522 | {error, ldap_connect_error}; 523 | with_login(Creds, Servers, Opts, Fun, RetriesLeft) -> 524 | case get_or_create_conn(Creds == anon, Servers, Opts) of 525 | {ok, {ConnType, LDAP}} -> 526 | Result = case Creds of 527 | anon -> 528 | ?L1("anonymous bind", []), 529 | case call_ldap_fun(Fun, LDAP) of 530 | {error, ldap_closed} -> 531 | purge_connection(Creds, Servers, Opts), 532 | with_login(Creds, Servers, Opts, Fun, RetriesLeft - 1); 533 | Other -> Other 534 | end; 535 | {UserDN, Password} -> 536 | case eldap:simple_bind(LDAP, UserDN, Password) of 537 | ok -> 538 | ?L1("bind succeeded: ~s", 539 | [scrub_dn(UserDN, env(log))]), 540 | case call_ldap_fun(Fun, LDAP, UserDN) of 541 | {error, ldap_closed} -> 542 | with_login(Creds, Servers, Opts, Fun, RetriesLeft - 1); 543 | {error, {gen_tcp_error, _}} -> 544 | with_login(Creds, Servers, Opts, Fun, RetriesLeft - 1); 545 | Other -> Other 546 | end; 547 | {error, invalidCredentials} -> 548 | ?L1("bind returned \"invalid credentials\": ~s", 549 | [scrub_dn(UserDN, env(log))]), 550 | {refused, UserDN, []}; 551 | {error, ldap_closed} -> 552 | purge_connection(Creds, Servers, Opts), 553 | with_login(Creds, Servers, Opts, Fun, RetriesLeft - 1); 554 | {error, {gen_tcp_error, _}} -> 555 | purge_connection(Creds, Servers, Opts), 556 | with_login(Creds, Servers, Opts, Fun, RetriesLeft - 1); 557 | {error, E} -> 558 | ?L1("bind error: ~p ~p", 559 | [scrub_dn(UserDN, env(log)), E]), 560 | %% Do not report internal bind error to a client 561 | {error, ldap_bind_error} 562 | end 563 | end, 564 | ok = case ConnType of 565 | eldap_transient -> eldap:close(LDAP); 566 | _ -> ok 567 | end, 568 | Result; 569 | Error -> 570 | ?L1("connect error: ~p", [Error]), 571 | case Error of 572 | {error, {gen_tcp_error, _}} -> Error; 573 | %% Do not report internal connection error to a client 574 | _Other -> {error, ldap_connect_error} 575 | end 576 | end. 577 | 578 | purge_connection(Creds, Servers, Opts) -> 579 | %% purge and retry with a new connection 580 | rabbit_log_ldap:warning("TCP connection to a LDAP server was closed or otherwise defunct."), 581 | purge_conn(Creds == anon, Servers, Opts), 582 | rabbit_log_ldap:warning("LDAP will retry with a new connection."). 583 | 584 | call_ldap_fun(Fun, LDAP) -> 585 | call_ldap_fun(Fun, LDAP, ""). 586 | 587 | call_ldap_fun(Fun, LDAP, UserDN) -> 588 | case Fun(LDAP) of 589 | {error, ldap_closed} -> 590 | %% LDAP connection was close, let with_login/5 retry 591 | {error, ldap_closed}; 592 | {error, {gen_tcp_error, E}} -> 593 | %% ditto 594 | {error, {gen_tcp_error, E}}; 595 | {error, E} -> 596 | ?L1("evaluate error: ~s ~p", [scrub_dn(UserDN, env(log)), E]), 597 | {error, ldap_evaluate_error}; 598 | Other -> Other 599 | end. 600 | 601 | %% Gets either the anonymous or bound (authenticated) connection 602 | get_or_create_conn(IsAnon, Servers, Opts) -> 603 | Conns = case get(ldap_conns) of 604 | undefined -> #{}; 605 | Dict -> Dict 606 | end, 607 | Key = {IsAnon, Servers, Opts}, 608 | case maps:find(Key, Conns) of 609 | {ok, Conn} -> 610 | Timeout = rabbit_misc:pget(idle_timeout, Opts, infinity), 611 | %% Defer the timeout by re-setting it. 612 | set_connection_timeout(Key, Timeout), 613 | {ok, {eldap_pooled, Conn}}; 614 | error -> 615 | {Timeout, EldapOpts} = case lists:keytake(idle_timeout, 1, Opts) of 616 | false -> {infinity, Opts}; 617 | {value, {idle_timeout, T}, EOpts} -> {T, EOpts} 618 | end, 619 | case {eldap_open(Servers, EldapOpts), Timeout} of 620 | %% If the timeout was set to 0, treat it as a one-off connection. 621 | %% See rabbitmq/rabbitmq-auth-backend-ldap#120 for background. 622 | {{ok, Conn}, 0} -> 623 | {ok, {eldap_transient, Conn}}; 624 | %% Non-zero timeout, put it in the pool 625 | {{ok, Conn}, Timeout} -> 626 | put(ldap_conns, maps:put(Key, Conn, Conns)), 627 | set_connection_timeout(Key, Timeout), 628 | {ok, {eldap_pooled, Conn}}; 629 | {Error, _} -> 630 | Error 631 | end 632 | end. 633 | 634 | set_connection_timeout(_, infinity) -> 635 | ok; 636 | set_connection_timeout(Key, Timeout) when is_integer(Timeout) -> 637 | worker_pool_worker:set_timeout(Key, Timeout, 638 | fun() -> 639 | Conns = case get(ldap_conns) of 640 | undefined -> #{}; 641 | Dict -> Dict 642 | end, 643 | case maps:find(Key, Conns) of 644 | {ok, Conn} -> 645 | eldap:close(Conn), 646 | put(ldap_conns, maps:remove(Key, Conns)); 647 | _ -> ok 648 | end 649 | end). 650 | 651 | %% Get attribute(s) from eldap entry 652 | get_attributes(_AttrName, []) -> {error, not_found}; 653 | get_attributes(AttrName, [#eldap_entry{attributes = A}|Rem]) -> 654 | case pget(AttrName, A) of 655 | [Attr|[]] -> Attr; 656 | Attrs when length(Attrs) > 1 -> Attrs; 657 | _ -> get_attributes(AttrName, Rem) 658 | end; 659 | get_attributes(AttrName, [_|Rem]) -> get_attributes(AttrName, Rem). 660 | 661 | %% Format multiple attribute values for logging 662 | format_multi_attr(Attrs) -> 663 | format_multi_attr(io_lib:printable_list(Attrs), Attrs). 664 | 665 | format_multi_attr(true, Attrs) -> Attrs; 666 | format_multi_attr(_, Attrs) when is_list(Attrs) -> string:join(Attrs, "; "); 667 | format_multi_attr(_, Error) -> Error. 668 | 669 | 670 | %% In case of multiple attributes, check for equality bi-directionally 671 | is_multi_attr_member(Str1, Str2) -> 672 | lists:member(Str1, Str2) orelse lists:member(Str2, Str1). 673 | 674 | purge_conn(IsAnon, Servers, Opts) -> 675 | Conns = get(ldap_conns), 676 | Key = {IsAnon, Servers, Opts}, 677 | {ok, Conn} = maps:find(Key, Conns), 678 | rabbit_log_ldap:warning("LDAP will purge an already closed or defunct LDAP server connection from the pool"), 679 | % We cannot close the connection with eldap:close/1 because as of OTP-13327 680 | % eldap will try to do_unbind first and will fail with a `{gen_tcp_error, closed}`. 681 | % Since we know that the connection is already closed, we just 682 | % kill its process. 683 | unlink(Conn), 684 | exit(Conn, closed), 685 | put(ldap_conns, maps:remove(Key, Conns)), 686 | ok. 687 | 688 | eldap_open(Servers, Opts) -> 689 | case eldap:open(Servers, ssl_conf() ++ Opts) of 690 | {ok, LDAP} -> 691 | TLS = env(use_starttls), 692 | case {TLS, at_least("5.10.4")} of %%R16B03 693 | {false, _} -> {ok, LDAP}; 694 | {true, false} -> exit({starttls_requires_min_r16b3}); 695 | {true, _} -> TLSOpts = ssl_options(), 696 | ELDAP = eldap, %% Fool xref 697 | case ELDAP:start_tls(LDAP, TLSOpts) of 698 | ok -> {ok, LDAP}; 699 | Error -> Error 700 | end 701 | end; 702 | Error -> 703 | Error 704 | end. 705 | 706 | ssl_conf() -> 707 | %% We must make sure not to add SSL options unless a) we have at least R16A 708 | %% b) we have SSL turned on (or it breaks StartTLS...) 709 | case env(use_ssl) of 710 | false -> [{ssl, false}]; 711 | true -> %% Only the unfixed version can be [] 712 | case {env(ssl_options), at_least("5.10")} of %% R16A 713 | {_, true} -> [{ssl, true}, {sslopts, ssl_options()}]; 714 | {[], _} -> [{ssl, true}]; 715 | {_, false} -> exit({ssl_options_requires_min_r16a}) 716 | end 717 | end. 718 | 719 | ssl_options() -> 720 | rabbit_networking:fix_ssl_options(env(ssl_options)). 721 | 722 | at_least(Ver) -> 723 | rabbit_misc:version_compare(erlang:system_info(version), Ver) =/= lt. 724 | 725 | % Note: 726 | % Default is configured in the Makefile 727 | get_expected_env_str(Key, Default) -> 728 | V = case env(Key) of 729 | Default -> 730 | rabbit_log_ldap:warning("rabbitmq_auth_backend_ldap configuration key '~p' is set to " 731 | "the default value of '~p', expected to get a non-default value~n", 732 | [Key, Default]), 733 | Default; 734 | V0 -> 735 | V0 736 | end, 737 | rabbit_data_coercion:to_list(V). 738 | 739 | env(F) -> 740 | {ok, V} = application:get_env(rabbitmq_auth_backend_ldap, F), 741 | V. 742 | 743 | login_fun(User, UserDN, Password, AuthProps) -> 744 | fun(L) -> case pget(vhost, AuthProps) of 745 | undefined -> do_login(User, UserDN, Password, L); 746 | VHost -> do_login(User, UserDN, Password, VHost, L) 747 | end 748 | end. 749 | 750 | do_login(Username, PrebindUserDN, Password, LDAP) -> 751 | do_login(Username, PrebindUserDN, Password, <<>>, LDAP). 752 | 753 | do_login(Username, PrebindUserDN, Password, VHost, LDAP) -> 754 | UserDN = case PrebindUserDN of 755 | unknown -> username_to_dn(Username, LDAP, dn_lookup_when()); 756 | _ -> PrebindUserDN 757 | end, 758 | User = #auth_user{username = Username, 759 | impl = #impl{user_dn = UserDN, 760 | password = Password}}, 761 | DTQ = fun (LDAPn) -> do_tag_queries(Username, UserDN, User, VHost, LDAPn) end, 762 | TagRes = case env(other_bind) of 763 | as_user -> DTQ(LDAP); 764 | _ -> with_ldap(creds(User), DTQ) 765 | end, 766 | case TagRes of 767 | {ok, L} -> {ok, User#auth_user{tags = [Tag || {Tag, true} <- L]}}; 768 | E -> E 769 | end. 770 | 771 | do_tag_queries(Username, UserDN, User, VHost, LDAP) -> 772 | {ok, [begin 773 | ?L1("CHECK: does ~s have tag ~s?", [Username, Tag]), 774 | VhostArgs = vhost_if_defined(VHost), 775 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 776 | EvalArgs = [{username, Username}, {user_dn, UserDN}] ++ VhostArgs ++ ADArgs, 777 | R = evaluate(Q, EvalArgs, User, LDAP), 778 | ?L1("DECISION: does ~s have tag ~s? ~p", 779 | [Username, Tag, R]), 780 | {Tag, R} 781 | end || {Tag, Q} <- env(tag_queries)]}. 782 | 783 | vhost_if_defined([]) -> []; 784 | vhost_if_defined(<<>>) -> []; 785 | vhost_if_defined(VHost) -> [{vhost, VHost}]. 786 | 787 | dn_lookup_when() -> 788 | case {env(dn_lookup_attribute), env(dn_lookup_bind)} of 789 | {none, _} -> 790 | never; 791 | {_, as_user} -> 792 | postbind; 793 | %% make it more obvious what the invariants are, 794 | %% see rabbitmq/rabbitmq-auth-backend-ldap#94. MK. 795 | {_, anon} -> 796 | prebind; 797 | {_, _} -> 798 | prebind 799 | end. 800 | 801 | username_to_dn_prebind(Username) -> 802 | with_ldap({ok, env(dn_lookup_bind)}, 803 | fun (LDAP) -> dn_lookup(Username, LDAP) end). 804 | 805 | username_to_dn(Username, LDAP, postbind) -> dn_lookup(Username, LDAP); 806 | username_to_dn(Username, _LDAP, _When) -> fill_user_dn_pattern(Username). 807 | 808 | dn_lookup(Username, LDAP) -> 809 | Filled = fill_user_dn_pattern(Username), 810 | DnLookupBase = get_expected_env_str(dn_lookup_base, none), 811 | DnLookupAttribute = get_expected_env_str(dn_lookup_attribute, none), 812 | case eldap:search(LDAP, 813 | [{base, DnLookupBase}, 814 | {filter, eldap:equalityMatch(DnLookupAttribute, Filled)}, 815 | {attributes, ["distinguishedName"]}]) of 816 | {ok, {referral, Referrals}} -> 817 | {error, {referrals_not_supported, Referrals}}; 818 | {ok, #eldap_search_result{entries = [#eldap_entry{object_name = DN}]}}-> 819 | ?L1("DN lookup: ~s -> ~s", [Username, DN]), 820 | DN; 821 | {ok, #eldap_search_result{entries = Entries}} -> 822 | rabbit_log_ldap:warning("Searching for DN for ~s, got back ~p~n", 823 | [Filled, Entries]), 824 | Filled; 825 | {error, _} = E -> 826 | exit(E) 827 | end. 828 | 829 | fill_user_dn_pattern(Username) -> 830 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 831 | fill(env(user_dn_pattern), [{username, Username}] ++ ADArgs). 832 | 833 | simple_bind_fill_pattern(Username) -> 834 | simple_bind_fill_pattern(env(user_bind_pattern), Username). 835 | 836 | simple_bind_fill_pattern(none, Username) -> 837 | fill_user_dn_pattern(Username); 838 | simple_bind_fill_pattern(Pattern, Username) -> 839 | ADArgs = rabbit_auth_backend_ldap_util:get_active_directory_args(Username), 840 | fill(Pattern, [{username, Username}] ++ ADArgs). 841 | 842 | creds(User) -> creds(User, env(other_bind)). 843 | 844 | creds(none, as_user) -> 845 | {error, "'other_bind' set to 'as_user' but no password supplied"}; 846 | creds(#auth_user{impl = #impl{user_dn = UserDN, password = PW}}, as_user) -> 847 | {ok, {UserDN, PW}}; 848 | creds(_, Creds) -> 849 | {ok, Creds}. 850 | 851 | %% Scrub credentials 852 | scrub_creds([], Acc) -> lists:reverse(Acc); 853 | scrub_creds([H|Rem], Acc) -> 854 | scrub_creds(Rem, [scrub_payload_creds(H)|Acc]). 855 | 856 | %% Scrub credentials from specific payloads 857 | scrub_payload_creds({'BindRequest', N, DN, {simple, _PWD}}) -> 858 | {'BindRequest', N, scrub_dn(DN), {simple, ?SCRUBBED_CREDENTIAL}}; 859 | scrub_payload_creds(Any) -> Any. 860 | 861 | scrub_dn(DN) -> scrub_dn(DN, network). 862 | 863 | scrub_dn(DN, network_unsafe) -> DN; 864 | scrub_dn(DN, false) -> DN; 865 | scrub_dn(DN, _) -> 866 | case is_dn(DN) of 867 | true -> scrub_rdn(string:tokens(DN, ","), []); 868 | _ -> 869 | %% We aren't fully certain its a DN, & don't know what sensitive 870 | %% info could be contained, thus just scrub the entire credential 871 | ?SCRUBBED_CREDENTIAL 872 | end. 873 | 874 | scrub_rdn([], Acc) -> 875 | string:join(lists:reverse(Acc), ","); 876 | scrub_rdn([DN|Rem], Acc) -> 877 | DN0 = case catch string:tokens(DN, "=") of 878 | L = [RDN, _] -> case string:to_lower(RDN) of 879 | "cn" -> [RDN, ?SCRUBBED_CREDENTIAL]; 880 | "dc" -> [RDN, ?SCRUBBED_CREDENTIAL]; 881 | "ou" -> [RDN, ?SCRUBBED_CREDENTIAL]; 882 | "uid" -> [RDN, ?SCRUBBED_CREDENTIAL]; 883 | _ -> L 884 | end; 885 | _Any -> 886 | %% There's no RDN, log "xxxx=xxxx" 887 | [?SCRUBBED_CREDENTIAL, ?SCRUBBED_CREDENTIAL] 888 | end, 889 | scrub_rdn(Rem, [string:join(DN0, "=")|Acc]). 890 | 891 | is_dn(S) when is_list(S) -> 892 | case catch string:tokens(to_list(S), "=") of 893 | L when length(L) > 1 -> true; 894 | _ -> false 895 | end; 896 | is_dn(_S) -> false. 897 | 898 | to_list(S) when is_list(S) -> S; 899 | to_list(S) when is_binary(S) -> binary_to_list(S); 900 | to_list(S) when is_atom(S) -> atom_to_list(S); 901 | to_list(S) -> {error, {badarg, S}}. 902 | 903 | log(Fmt, Args) -> case env(log) of 904 | false -> ok; 905 | _ -> rabbit_log_ldap:info(Fmt ++ "~n", Args) 906 | end. 907 | 908 | fill(Fmt, Args) -> 909 | ?L2("filling template \"~s\" with~n ~p", [Fmt, Args]), 910 | R = rabbit_auth_backend_ldap_util:fill(Fmt, Args), 911 | ?L2("template result: \"~s\"", [R]), 912 | R. 913 | 914 | log_result({ok, #auth_user{}}) -> ok; 915 | log_result(true) -> ok; 916 | log_result(false) -> denied; 917 | log_result({refused, _, _}) -> denied; 918 | log_result(E) -> E. 919 | 920 | log_user(#auth_user{username = U}) -> rabbit_misc:format("\"~s\"", [U]). 921 | 922 | log_vhost(Args) -> 923 | rabbit_misc:format("access to vhost \"~s\"", [pget(vhost, Args)]). 924 | 925 | log_resource(Args) -> 926 | rabbit_misc:format("~s permission for ~s \"~s\" in \"~s\"", 927 | [pget(permission, Args), pget(resource, Args), 928 | pget(name, Args), pget(vhost, Args)]). 929 | -------------------------------------------------------------------------------- /src/rabbit_auth_backend_ldap_app.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_auth_backend_ldap_app). 9 | 10 | -behaviour(application). 11 | -export([start/2, stop/1]). 12 | 13 | %% Dummy supervisor to get this application behaviour working 14 | -behaviour(supervisor). 15 | -export([create_ldap_pool/0, init/1]). 16 | 17 | -rabbit_boot_step({ldap_pool, 18 | [{description, "LDAP pool"}, 19 | {mfa, {?MODULE, create_ldap_pool, []}}, 20 | {requires, kernel_ready}]}). 21 | 22 | create_ldap_pool() -> 23 | {ok, PoolSize} = application:get_env(rabbitmq_auth_backend_ldap, pool_size), 24 | rabbit_sup:start_supervisor_child(ldap_pool_sup, worker_pool_sup, [PoolSize, ldap_pool]). 25 | 26 | start(_Type, _StartArgs) -> 27 | {ok, Backends} = application:get_env(rabbit, auth_backends), 28 | case configured(rabbit_auth_backend_ldap, Backends) of 29 | true -> ok; 30 | false -> rabbit_log_ldap:warning( 31 | "LDAP plugin loaded, but rabbit_auth_backend_ldap is not " 32 | "in the list of auth_backends. LDAP auth will not work.") 33 | end, 34 | {ok, SSL} = application:get_env(rabbitmq_auth_backend_ldap, use_ssl), 35 | {ok, TLS} = application:get_env(rabbitmq_auth_backend_ldap, use_starttls), 36 | case SSL orelse TLS of 37 | true -> 38 | rabbit_networking:ensure_ssl(), 39 | ok; 40 | false -> ok 41 | end, 42 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 43 | 44 | stop(_State) -> 45 | ok. 46 | 47 | configured(_M, []) -> false; 48 | configured(M, [M |_]) -> true; 49 | configured(M, [{M,_}|_]) -> true; 50 | configured(M, [{_,M}|_]) -> true; 51 | configured(M, [_ |T]) -> configured(M, T). 52 | 53 | %%---------------------------------------------------------------------------- 54 | 55 | init([]) -> {ok, {{one_for_one, 3, 10}, []}}. 56 | -------------------------------------------------------------------------------- /src/rabbit_auth_backend_ldap_util.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_auth_backend_ldap_util). 9 | 10 | -export([fill/2, get_active_directory_args/1]). 11 | 12 | fill(Fmt, []) -> 13 | binary_to_list(iolist_to_binary(Fmt)); 14 | 15 | fill(Fmt, [{K, V} | T]) -> 16 | Var = [[$\\, $$, ${] ++ atom_to_list(K) ++ [$}]], 17 | fill(re:replace(Fmt, Var, [to_repl(V)], [global]), T). 18 | 19 | to_repl(V) when is_atom(V) -> to_repl(atom_to_list(V)); 20 | to_repl(V) when is_binary(V) -> to_repl(binary_to_list(V)); 21 | to_repl([]) -> []; 22 | to_repl([$\\ | T]) -> [$\\, $\\ | to_repl(T)]; 23 | to_repl([$& | T]) -> [$\\, $& | to_repl(T)]; 24 | to_repl([H | T]) -> [H | to_repl(T)]; 25 | to_repl(_) -> []. % fancy variables like peer IP are just ignored 26 | 27 | get_active_directory_args([ADDomain, ADUser]) -> 28 | [{ad_domain, ADDomain}, {ad_user, ADUser}]; 29 | get_active_directory_args(Parts) when is_list(Parts) -> 30 | []; 31 | get_active_directory_args(Username) when is_binary(Username) -> 32 | % If Username is in Domain\User format, provide additional fill 33 | % template arguments 34 | get_active_directory_args(binary:split(Username, <<"\\">>, [trim_all])). 35 | -------------------------------------------------------------------------------- /test/config_schema_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(config_schema_SUITE). 9 | 10 | -compile(export_all). 11 | 12 | all() -> 13 | [ 14 | run_snippets 15 | ]. 16 | 17 | %% ------------------------------------------------------------------- 18 | %% Testsuite setup/teardown. 19 | %% ------------------------------------------------------------------- 20 | 21 | init_per_suite(Config) -> 22 | rabbit_ct_helpers:log_environment(), 23 | Config1 = rabbit_ct_helpers:run_setup_steps(Config), 24 | rabbit_ct_config_schema:init_schemas(rabbitmq_auth_backend_ldap, Config1). 25 | 26 | 27 | end_per_suite(Config) -> 28 | rabbit_ct_helpers:run_teardown_steps(Config). 29 | 30 | init_per_testcase(Testcase, Config) -> 31 | rabbit_ct_helpers:testcase_started(Config, Testcase), 32 | Config1 = rabbit_ct_helpers:set_config(Config, [ 33 | {rmq_nodename_suffix, Testcase} 34 | ]), 35 | rabbit_ct_helpers:run_steps(Config1, 36 | rabbit_ct_broker_helpers:setup_steps() ++ 37 | rabbit_ct_client_helpers:setup_steps()). 38 | 39 | end_per_testcase(Testcase, Config) -> 40 | Config1 = rabbit_ct_helpers:run_steps(Config, 41 | rabbit_ct_client_helpers:teardown_steps() ++ 42 | rabbit_ct_broker_helpers:teardown_steps()), 43 | rabbit_ct_helpers:testcase_finished(Config1, Testcase). 44 | 45 | %% ------------------------------------------------------------------- 46 | %% Testcases. 47 | %% ------------------------------------------------------------------- 48 | 49 | run_snippets(Config) -> 50 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 51 | ?MODULE, run_snippets1, [Config]). 52 | 53 | run_snippets1(Config) -> 54 | rabbit_ct_config_schema:run_snippets(Config). 55 | 56 | -------------------------------------------------------------------------------- /test/config_schema_SUITE_data/certs/cacert.pem: -------------------------------------------------------------------------------- 1 | I'm not a certificate 2 | -------------------------------------------------------------------------------- /test/config_schema_SUITE_data/certs/cert.pem: -------------------------------------------------------------------------------- 1 | I'm not a certificate 2 | -------------------------------------------------------------------------------- /test/config_schema_SUITE_data/certs/key.pem: -------------------------------------------------------------------------------- 1 | I'm not a certificate 2 | -------------------------------------------------------------------------------- /test/config_schema_SUITE_data/rabbitmq_auth_backend_ldap.snippets: -------------------------------------------------------------------------------- 1 | [{ldap_servers, 2 | "auth_ldap.servers.1 = DC1.domain.com 3 | auth_ldap.servers.2 = DC1.eng.domain.com", 4 | [{rabbitmq_auth_backend_ldap, 5 | [{servers,["DC1.domain.com","DC1.eng.domain.com"]}]}], 6 | [rabbitmq_auth_backend_ldap]}, 7 | {ldap_servers_short, 8 | "auth_ldap.servers.1 = hostname1 9 | auth_ldap.servers.2 = hostname2", 10 | [{rabbitmq_auth_backend_ldap,[{servers,["hostname1","hostname2"]}]}], 11 | [rabbitmq_auth_backend_ldap]}, 12 | 13 | {ldap_port, 14 | "auth_ldap.port = 1234", 15 | [{rabbitmq_auth_backend_ldap,[ 16 | {port, 1234} 17 | ]}], 18 | [rabbitmq_auth_backend_ldap]}, 19 | 20 | {ldap_connection_pool_size, 21 | "auth_ldap.connection_pool_size = 128", 22 | [{rabbitmq_auth_backend_ldap,[ 23 | {pool_size, 128} 24 | ]}], 25 | [rabbitmq_auth_backend_ldap]}, 26 | 27 | {ldap_servers_short, 28 | "auth_ldap.servers.1 = hostname1 29 | auth_ldap.servers.2 = hostname2", 30 | [{rabbitmq_auth_backend_ldap,[{servers,["hostname1","hostname2"]}]}], 31 | [rabbitmq_auth_backend_ldap]}, 32 | 33 | {ldap_timeouts, 34 | "auth_ldap.timeout = 50000 35 | auth_ldap.idle_timeout = 90000", 36 | [{rabbitmq_auth_backend_ldap,[ 37 | {timeout, 50000}, 38 | {idle_timeout, 90000} 39 | ]}], 40 | [rabbitmq_auth_backend_ldap]}, 41 | 42 | {ldap_use_ssl_true, 43 | "auth_ldap.use_ssl = true", 44 | [{rabbitmq_auth_backend_ldap,[ 45 | {use_ssl, true} 46 | ]}], 47 | [rabbitmq_auth_backend_ldap]}, 48 | 49 | {ldap_use_ssl_false, 50 | "auth_ldap.use_ssl = false", 51 | [{rabbitmq_auth_backend_ldap,[ 52 | {use_ssl, false} 53 | ]}], 54 | [rabbitmq_auth_backend_ldap]}, 55 | 56 | {ldap_use_starttls_true, 57 | "auth_ldap.use_starttls = true", 58 | [{rabbitmq_auth_backend_ldap,[ 59 | {use_starttls, true} 60 | ]}], 61 | [rabbitmq_auth_backend_ldap]}, 62 | 63 | {ldap_log_false, 64 | "auth_ldap.log = false", 65 | [{rabbitmq_auth_backend_ldap,[ 66 | {log, false} 67 | ]}], 68 | [rabbitmq_auth_backend_ldap]}, 69 | 70 | {ldap_log_true, 71 | "auth_ldap.log = true", 72 | [{rabbitmq_auth_backend_ldap,[ 73 | {log, true} 74 | ]}], 75 | [rabbitmq_auth_backend_ldap]}, 76 | 77 | {ldap_log_network, 78 | "auth_ldap.log = network", 79 | [{rabbitmq_auth_backend_ldap,[ 80 | {log, network} 81 | ]}], 82 | [rabbitmq_auth_backend_ldap]}, 83 | 84 | {ldap_log_network_unsafe, 85 | "auth_ldap.log = network_unsafe", 86 | [{rabbitmq_auth_backend_ldap,[ 87 | {log, network_unsafe} 88 | ]}], 89 | [rabbitmq_auth_backend_ldap]}, 90 | 91 | {user_dn_pattern, 92 | "auth_ldap.user_dn_pattern = ${ad_user}-${ad_domain}", 93 | [{rabbitmq_auth_backend_ldap, 94 | [{user_dn_pattern, "${ad_user}-${ad_domain}"}]}], 95 | [rabbitmq_auth_backend_ldap]}, 96 | 97 | {user_bind_pattern, 98 | "auth_ldap.user_bind_pattern = ${ad_user}-${ad_domain}", 99 | [{rabbitmq_auth_backend_ldap, 100 | [{user_bind_pattern, "${ad_user}-${ad_domain}"}]}], 101 | [rabbitmq_auth_backend_ldap]}, 102 | 103 | {group_lookup_base, 104 | "auth_ldap.group_lookup_base = DC=gopivotal,DC=com", 105 | [{rabbitmq_auth_backend_ldap, 106 | [{group_lookup_base, "DC=gopivotal,DC=com"}]}], 107 | [rabbitmq_auth_backend_ldap]}, 108 | 109 | {dn_lookup, 110 | "auth_ldap.dn_lookup_attribute = userPrincipalName 111 | auth_ldap.dn_lookup_base = DC=gopivotal,DC=com 112 | auth_ldap.dn_lookup_bind = as_user", 113 | [{rabbitmq_auth_backend_ldap, 114 | [{dn_lookup_attribute,"userPrincipalName"}, 115 | {dn_lookup_base,"DC=gopivotal,DC=com"}, 116 | {dn_lookup_bind,as_user}]}], 117 | [rabbitmq_auth_backend_ldap]}, 118 | 119 | {db_lookup_bind, 120 | "auth_ldap.dn_lookup_bind.user_dn = username 121 | auth_ldap.dn_lookup_bind.password = password", 122 | [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,{"username","password"}}]}], 123 | [rabbitmq_auth_backend_ldap]}, 124 | 125 | {db_lookup_bind_anon, 126 | "auth_ldap.dn_lookup_bind = anon", 127 | [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,anon}]}], 128 | [rabbitmq_auth_backend_ldap]}, 129 | 130 | {other_bind_anon, 131 | "auth_ldap.other_bind = anon", 132 | [{rabbitmq_auth_backend_ldap,[{other_bind,anon}]}], 133 | [rabbitmq_auth_backend_ldap]}, 134 | 135 | {both_binds_anon, 136 | "auth_ldap.dn_lookup_bind = anon 137 | auth_ldap.other_bind = anon", 138 | [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,anon}, 139 | {other_bind,anon}]}], 140 | [rabbitmq_auth_backend_ldap]}, 141 | 142 | {other_bind_as_user, 143 | "auth_ldap.other_bind = as_user", 144 | [{rabbitmq_auth_backend_ldap,[{other_bind,as_user}]}], 145 | [rabbitmq_auth_backend_ldap]}, 146 | 147 | {other_bind_pass, 148 | "auth_ldap.other_bind.user_dn = username 149 | auth_ldap.other_bind.password = password", 150 | [{rabbitmq_auth_backend_ldap,[{other_bind,{"username","password"}}]}], 151 | [rabbitmq_auth_backend_ldap]}, 152 | 153 | {ssl_options, 154 | "auth_ldap.use_ssl = true 155 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 156 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 157 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 158 | auth_ldap.ssl_options.verify = verify_peer 159 | auth_ldap.ssl_options.fail_if_no_peer_cert = true", 160 | [{rabbitmq_auth_backend_ldap, [ 161 | {use_ssl, true}, 162 | {ssl_options, 163 | [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"}, 164 | {certfile, "test/config_schema_SUITE_data/certs/cert.pem"}, 165 | {keyfile, "test/config_schema_SUITE_data/certs/key.pem"}, 166 | {verify, verify_peer}, 167 | {fail_if_no_peer_cert, true}]} 168 | ]}], 169 | [rabbitmq_auth_backend_ldap]}, 170 | 171 | {ssl_options_verify_peer, 172 | "auth_ldap.use_ssl = true 173 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 174 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 175 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 176 | auth_ldap.ssl_options.verify = verify_peer 177 | auth_ldap.ssl_options.fail_if_no_peer_cert = false", 178 | [{rabbitmq_auth_backend_ldap, 179 | [{use_ssl, true}, 180 | {ssl_options, 181 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 182 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 183 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 184 | {verify,verify_peer}, 185 | {fail_if_no_peer_cert,false}]}]}], 186 | []}, 187 | {ssl_options_password, 188 | "auth_ldap.use_ssl = true 189 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 190 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 191 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 192 | auth_ldap.ssl_options.password = t0p$3kRe7", 193 | [{rabbitmq_auth_backend_ldap, 194 | [{use_ssl, true}, 195 | {ssl_options, 196 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 197 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 198 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 199 | {password,"t0p$3kRe7"}]}]}], 200 | []}, 201 | {ssl_options_tls_versions, 202 | "auth_ldap.use_ssl = true 203 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 204 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 205 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 206 | auth_ldap.ssl_options.versions.tls1_2 = tlsv1.2 207 | auth_ldap.ssl_options.versions.tls1_1 = tlsv1.1", 208 | [], 209 | [{rabbitmq_auth_backend_ldap, 210 | [{ssl_options, 211 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 212 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 213 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 214 | {versions,['tlsv1.2','tlsv1.1']}]}, 215 | {use_ssl, true}]}], 216 | []}, 217 | {ssl_options_depth, 218 | "auth_ldap.use_ssl = true 219 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 220 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 221 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 222 | auth_ldap.ssl_options.depth = 2 223 | auth_ldap.ssl_options.verify = verify_peer 224 | auth_ldap.ssl_options.fail_if_no_peer_cert = false", 225 | [{rabbitmq_auth_backend_ldap, 226 | [{use_ssl, true}, 227 | {ssl_options, 228 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 229 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 230 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 231 | {depth,2}, 232 | {verify,verify_peer}, 233 | {fail_if_no_peer_cert,false}]}]}], 234 | []}, 235 | {ssl_options_honor_cipher_order, 236 | "auth_ldap.use_ssl = true 237 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 238 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 239 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 240 | auth_ldap.ssl_options.depth = 2 241 | auth_ldap.ssl_options.verify = verify_peer 242 | auth_ldap.ssl_options.fail_if_no_peer_cert = false 243 | auth_ldap.ssl_options.honor_cipher_order = true", 244 | [{rabbitmq_auth_backend_ldap, 245 | [{use_ssl, true}, 246 | {ssl_options, 247 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 248 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 249 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 250 | {depth,2}, 251 | {verify,verify_peer}, 252 | {fail_if_no_peer_cert, false}, 253 | {honor_cipher_order, true}]}]}], 254 | []}, 255 | {ssl_options_honor_ecc_order, 256 | "auth_ldap.use_ssl = true 257 | auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem 258 | auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem 259 | auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem 260 | auth_ldap.ssl_options.depth = 2 261 | auth_ldap.ssl_options.verify = verify_peer 262 | auth_ldap.ssl_options.fail_if_no_peer_cert = false 263 | auth_ldap.ssl_options.honor_ecc_order = true", 264 | [{rabbitmq_auth_backend_ldap, 265 | [{use_ssl, true}, 266 | {ssl_options, 267 | [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, 268 | {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, 269 | {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, 270 | {depth,2}, 271 | {verify,verify_peer}, 272 | {fail_if_no_peer_cert, false}, 273 | {honor_ecc_order, true}]}]}], 274 | []} 275 | 276 | ]. 277 | -------------------------------------------------------------------------------- /test/rabbit_ldap_seed.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(rabbit_ldap_seed). 9 | 10 | -include_lib("eldap/include/eldap.hrl"). 11 | 12 | -export([seed/1,delete/1]). 13 | 14 | seed(Logon) -> 15 | H = connect(Logon), 16 | ok = add(H, rabbitmq_com()), 17 | ok = add(H, ou("people")), 18 | [ add(H, P) || P <- people() ], 19 | ok = add(H, ou("vhosts")), 20 | ok = add(H, test()), 21 | ok = add(H, ou("groups")), 22 | [ add(H, P) || P <- groups() ], 23 | eldap:close(H), 24 | ok. 25 | 26 | rabbitmq_com() -> 27 | {"dc=rabbitmq,dc=com", 28 | [{"objectClass", ["dcObject", "organization"]}, 29 | {"dc", ["rabbitmq"]}, 30 | {"o", ["Test"]}]}. 31 | 32 | 33 | delete(Logon) -> 34 | H = connect(Logon), 35 | eldap:delete(H, "ou=test,dc=rabbitmq,dc=com"), 36 | eldap:delete(H, "ou=test,ou=vhosts,dc=rabbitmq,dc=com"), 37 | eldap:delete(H, "ou=vhosts,dc=rabbitmq,dc=com"), 38 | [ eldap:delete(H, P) || {P, _} <- groups() ], 39 | [ eldap:delete(H, P) || {P, _} <- people() ], 40 | eldap:delete(H, "ou=groups,dc=rabbitmq,dc=com"), 41 | eldap:delete(H, "ou=people,dc=rabbitmq,dc=com"), 42 | eldap:delete(H, "dc=rabbitmq,dc=com"), 43 | eldap:close(H), 44 | ok. 45 | 46 | people() -> 47 | [ bob(), 48 | dominic(), 49 | charlie(), 50 | edward(), 51 | johndoe(), 52 | alice(), 53 | peter(), 54 | carol(), 55 | jimmy() 56 | ]. 57 | 58 | groups() -> 59 | [wheel_group(), 60 | people_group(), 61 | staff_group(), 62 | bobs_group(), 63 | bobs2_group(), 64 | admins_group() 65 | ]. 66 | 67 | wheel_group() -> 68 | {A, _} = alice(), 69 | {C, _} = charlie(), 70 | {D, _} = dominic(), 71 | {P, _} = peter(), 72 | {"cn=wheel,ou=groups,dc=rabbitmq,dc=com", 73 | [{"objectClass", ["groupOfNames"]}, 74 | {"cn", ["wheel"]}, 75 | {"member", [A, C, D, P]}]}. 76 | 77 | people_group() -> 78 | {C, _} = charlie(), 79 | {D, _} = dominic(), 80 | {P, _} = peter(), 81 | {"cn=people,ou=groups,dc=rabbitmq,dc=com", 82 | [{"objectClass", ["groupOfNames"]}, 83 | {"cn", ["people"]}, 84 | {"member", [C, D, P]}]}. 85 | 86 | staff_group() -> 87 | {C, _} = charlie(), 88 | {D, _} = dominic(), 89 | {P, _} = peter(), 90 | {"cn=staff,ou=groups,dc=rabbitmq,dc=com", 91 | [{"objectClass", ["groupOfNames"]}, 92 | {"cn", ["people"]}, 93 | {"member", [C, D, P]}]}. 94 | 95 | bobs_group() -> 96 | {B, _} = bob(), 97 | {"cn=bobs,ou=groups,dc=rabbitmq,dc=com", 98 | [{"objectClass", ["groupOfNames"]}, 99 | {"cn", ["bobs"]}, 100 | {"member", [B]}]}. 101 | 102 | bobs2_group() -> 103 | {B, _} = bobs_group(), 104 | {"cn=bobs2,ou=groups,dc=rabbitmq,dc=com", 105 | [{"objectClass", ["groupOfNames"]}, 106 | {"cn", ["bobs2"]}, 107 | {"member", [B]}]}. 108 | 109 | admins_group() -> 110 | {B, _} = bobs2_group(), 111 | {W, _} = wheel_group(), 112 | {"cn=admins,ou=groups,dc=rabbitmq,dc=com", 113 | [{"objectClass", ["groupOfNames"]}, 114 | {"cn", ["admins"]}, 115 | {"member", [B, W]}]}. 116 | 117 | person(Cn, Sn) -> 118 | {"cn="++Cn++",ou=people,dc=rabbitmq,dc=com", 119 | [{"objectClass", ["person"]}, 120 | {"cn", [Cn]}, 121 | {"sn", [Sn]}, 122 | {"userPassword", ["password"]}]}. 123 | 124 | bob() -> person("Bob", "Robert"). 125 | dominic() -> person("Dominic", "Dom"). 126 | charlie() -> person("Charlie", "Charlie Boy"). 127 | edward() -> person("Edward", "Ed"). 128 | johndoe() -> person("John Doe", "Doe"). 129 | 130 | alice() -> 131 | {"cn=Alice,ou=people,dc=rabbitmq,dc=com", 132 | [{"objectClass", ["person"]}, 133 | {"cn", ["Alice"]}, 134 | {"sn", ["Ali"]}, 135 | {"userPassword", ["password"]}, 136 | {"description", ["can-declare-queues"]}]}. 137 | 138 | peter() -> 139 | {"uid=peter,ou=people,dc=rabbitmq,dc=com", 140 | [{"cn", ["Peter"]}, 141 | {"givenName", ["Peter"]}, 142 | {"sn", ["Jones"]}, 143 | {"uid", ["peter"]}, 144 | {"uidNumber", ["5000"]}, 145 | {"gidNumber", ["10000"]}, 146 | {"homeDirectory", ["/home/peter"]}, 147 | {"mail", ["peter.jones@rabbitmq.com"]}, 148 | {"objectClass", ["top", 149 | "posixAccount", 150 | "shadowAccount", 151 | "inetOrgPerson", 152 | "organizationalPerson", 153 | "person"]}, 154 | {"loginShell", ["/bin/bash"]}, 155 | {"userPassword", ["password"]}, 156 | {"memberOf", ["cn=wheel,ou=groups,dc=rabbitmq,dc=com", 157 | "cn=staff,ou=groups,dc=rabbitmq,dc=com", 158 | "cn=people,ou=groups,dc=rabbitmq,dc=com"]}]}. 159 | 160 | carol() -> 161 | {"uid=carol,ou=people,dc=rabbitmq,dc=com", 162 | [{"cn", ["Carol"]}, 163 | {"givenName", ["Carol"]}, 164 | {"sn", ["Meyers"]}, 165 | {"uid", ["peter"]}, 166 | {"uidNumber", ["655"]}, 167 | {"gidNumber", ["10000"]}, 168 | {"homeDirectory", ["/home/carol"]}, 169 | {"mail", ["carol.meyers@example.com"]}, 170 | {"objectClass", ["top", 171 | "posixAccount", 172 | "shadowAccount", 173 | "inetOrgPerson", 174 | "organizationalPerson", 175 | "person"]}, 176 | {"loginShell", ["/bin/bash"]}, 177 | {"userPassword", ["password"]}]}. 178 | 179 | % rabbitmq/rabbitmq-auth-backend-ldap#100 180 | jimmy() -> 181 | {"cn=Jimmy,ou=people,dc=rabbitmq,dc=com", 182 | [{"objectClass", ["person"]}, 183 | {"cn", ["Jimmy"]}, 184 | {"sn", ["Makes"]}, 185 | {"userPassword", ["password"]}, 186 | {"description", ["^RMQ-foobar", "^RMQ-.*$"]}]}. 187 | 188 | add(H, {A, B}) -> 189 | ok = eldap:add(H, A, B). 190 | 191 | connect({Host, Port}) -> 192 | {ok, H} = eldap:open([Host], [{port, Port}]), 193 | ok = eldap:simple_bind(H, "cn=admin,dc=rabbitmq,dc=com", "admin"), 194 | H. 195 | 196 | ou(Name) -> 197 | {"ou=" ++ Name ++ ",dc=rabbitmq,dc=com", [{"objectClass", ["organizationalUnit"]}, {"ou", [Name]}]}. 198 | 199 | test() -> 200 | {"ou=test,ou=vhosts,dc=rabbitmq,dc=com", [{"objectClass", ["top", "organizationalUnit"]}, {"ou", ["test"]}]}. 201 | 202 | -------------------------------------------------------------------------------- /test/system_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(system_SUITE). 9 | -compile([export_all]). 10 | 11 | -include_lib("common_test/include/ct.hrl"). 12 | -include_lib("eunit/include/eunit.hrl"). 13 | -include_lib("amqp_client/include/amqp_client.hrl"). 14 | 15 | -define(ALICE_NAME, "Alice"). 16 | -define(BOB_NAME, "Bob"). 17 | -define(CAROL_NAME, "Carol"). 18 | -define(PETER_NAME, "Peter"). 19 | -define(JIMMY_NAME, "Jimmy"). 20 | 21 | -define(VHOST, "test"). 22 | 23 | -define(ALICE, #amqp_params_network{username = <>, 24 | password = <<"password">>, 25 | virtual_host = <>}). 26 | 27 | -define(BOB, #amqp_params_network{username = <>, 28 | password = <<"password">>, 29 | virtual_host = <>}). 30 | 31 | -define(CAROL, #amqp_params_network{username = <>, 32 | password = <<"password">>, 33 | virtual_host = <>}). 34 | 35 | -define(PETER, #amqp_params_network{username = <>, 36 | password = <<"password">>, 37 | virtual_host = <>}). 38 | 39 | -define(JIMMY, #amqp_params_network{username = <>, 40 | password = <<"password">>, 41 | virtual_host = <>}). 42 | 43 | -define(BASE_CONF_RABBIT, {rabbit, [{default_vhost, <<"test">>}]}). 44 | 45 | base_conf_ldap(LdapPort, IdleTimeout, PoolSize) -> 46 | {rabbitmq_auth_backend_ldap, [{servers, ["localhost"]}, 47 | {user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, 48 | {other_bind, anon}, 49 | {use_ssl, false}, 50 | {port, LdapPort}, 51 | {idle_timeout, IdleTimeout}, 52 | {pool_size, PoolSize}, 53 | {log, true}, 54 | {group_lookup_base, "ou=groups,dc=rabbitmq,dc=com"}, 55 | {vhost_access_query, vhost_access_query_base()}, 56 | {resource_access_query, 57 | {for, [{resource, exchange, 58 | {for, [{permission, configure, 59 | {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} 60 | }, 61 | {permission, write, {constant, true}}, 62 | {permission, read, 63 | {match, {string, "${name}"}, 64 | {string, "^xch-${username}-.*"}} 65 | } 66 | ]}}, 67 | {resource, queue, 68 | {for, [{permission, configure, 69 | {match, {attribute, "${user_dn}", "description"}, 70 | {string, "can-declare-queues"}} 71 | }, 72 | {permission, write, {constant, true}}, 73 | {permission, read, 74 | {'or', 75 | [{'and', 76 | [{equals, "${name}", "test1"}, 77 | {equals, "${username}", "Alice"}]}, 78 | {'and', 79 | [{equals, "${name}", "test2"}, 80 | {'not', {equals, "${username}", "Bob"}}]} 81 | ]}} 82 | ]}} 83 | ]}}, 84 | {topic_access_query, topic_access_query_base()}, 85 | {tag_queries, [{monitor, {constant, true}}, 86 | {administrator, {constant, false}}, 87 | {management, {constant, false}}]} 88 | ]}. 89 | 90 | %%-------------------------------------------------------------------- 91 | 92 | all() -> 93 | [ 94 | {group, non_parallel_tests}, 95 | {group, with_idle_timeout} 96 | ]. 97 | 98 | groups() -> 99 | Tests = [ 100 | purge_connection, 101 | ldap_only, 102 | ldap_and_internal, 103 | internal_followed_ldap_and_internal, 104 | tag_attribution_ldap_only, 105 | tag_attribution_ldap_and_internal, 106 | tag_attribution_internal_followed_by_ldap_and_internal, 107 | invalid_or_clause_ldap_only, 108 | invalid_and_clause_ldap_only, 109 | topic_authorisation_publishing_ldap_only, 110 | topic_authorisation_consumption, 111 | match_bidirectional, 112 | match_bidirectional_gh_100 113 | ], 114 | [ 115 | {non_parallel_tests, [], Tests 116 | }, 117 | {with_idle_timeout, [], [connections_closed_after_timeout | Tests] 118 | } 119 | ]. 120 | 121 | suite() -> 122 | [{timetrap, {minutes, 2}}]. 123 | 124 | %% ------------------------------------------------------------------- 125 | %% Testsuite setup/teardown. 126 | %% ------------------------------------------------------------------- 127 | 128 | init_per_suite(Config) -> 129 | rabbit_ct_helpers:log_environment(), 130 | rabbit_ct_helpers:run_setup_steps(Config, [fun init_slapd/1]). 131 | 132 | end_per_suite(Config) -> 133 | rabbit_ct_helpers:run_teardown_steps(Config, [fun stop_slapd/1]). 134 | 135 | init_per_group(Group, Config) -> 136 | Config1 = rabbit_ct_helpers:set_config(Config, [ 137 | {rmq_nodename_suffix, Group} 138 | ]), 139 | LdapPort = ?config(ldap_port, Config), 140 | Config2 = rabbit_ct_helpers:merge_app_env(Config1, ?BASE_CONF_RABBIT), 141 | Config3 = rabbit_ct_helpers:merge_app_env(Config2, 142 | base_conf_ldap(LdapPort, 143 | idle_timeout(Group), 144 | pool_size(Group))), 145 | Logon = {"localhost", LdapPort}, 146 | rabbit_ldap_seed:delete(Logon), 147 | rabbit_ldap_seed:seed(Logon), 148 | Config4 = rabbit_ct_helpers:set_config(Config3, {ldap_port, LdapPort}), 149 | 150 | rabbit_ct_helpers:run_steps(Config4, 151 | rabbit_ct_broker_helpers:setup_steps() ++ 152 | rabbit_ct_client_helpers:setup_steps()). 153 | 154 | end_per_group(_, Config) -> 155 | rabbit_ldap_seed:delete({"localhost", ?config(ldap_port, Config)}), 156 | rabbit_ct_helpers:run_steps(Config, 157 | rabbit_ct_client_helpers:teardown_steps() ++ 158 | rabbit_ct_broker_helpers:teardown_steps()). 159 | 160 | init_slapd(Config) -> 161 | DataDir = ?config(data_dir, Config), 162 | PrivDir = ?config(priv_dir, Config), 163 | TcpPort = 25389, 164 | SlapdDir = filename:join([PrivDir, "openldap"]), 165 | InitSlapd = filename:join([DataDir, "init-slapd.sh"]), 166 | Cmd = [InitSlapd, SlapdDir, {"~b", [TcpPort]}], 167 | case rabbit_ct_helpers:exec(Cmd) of 168 | {ok, Stdout} -> 169 | {match, [SlapdPid]} = re:run( 170 | Stdout, 171 | "^SLAPD_PID=([0-9]+)$", 172 | [{capture, all_but_first, list}, 173 | multiline]), 174 | ct:pal(?LOW_IMPORTANCE, 175 | "slapd(8) PID: ~s~nslapd(8) listening on: ~b", 176 | [SlapdPid, TcpPort]), 177 | rabbit_ct_helpers:set_config(Config, 178 | [{slapd_pid, SlapdPid}, 179 | {ldap_port, TcpPort}]); 180 | _ -> 181 | _ = rabbit_ct_helpers:exec(["pkill", "-INT", "slapd"]), 182 | {skip, "Failed to initialize slapd(8)"} 183 | end. 184 | 185 | stop_slapd(Config) -> 186 | SlapdPid = ?config(slapd_pid, Config), 187 | Cmd = ["kill", "-INT", SlapdPid], 188 | _ = rabbit_ct_helpers:exec(Cmd), 189 | Config. 190 | 191 | idle_timeout(with_idle_timeout) -> 2000; 192 | idle_timeout(non_parallel_tests) -> infinity. 193 | 194 | pool_size(with_idle_timeout) -> 1; 195 | pool_size(non_parallel_tests) -> 10. 196 | 197 | init_internal(Config) -> 198 | ok = control_action(Config, add_user, [?ALICE_NAME, ""]), 199 | ok = control_action(Config, set_permissions, [?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]), 200 | ok = control_action(Config, set_user_tags, [?ALICE_NAME, "management", "foo"]), 201 | ok = control_action(Config, add_user, [?BOB_NAME, ""]), 202 | ok = control_action(Config, set_permissions, [?BOB_NAME, "^$", "^$", "^$"]), 203 | ok = control_action(Config, add_user, [?PETER_NAME, ""]), 204 | ok = control_action(Config, set_permissions, [?PETER_NAME, "^$", "^$", "^$"]). 205 | 206 | end_internal(Config) -> 207 | ok = control_action(Config, delete_user, [?ALICE_NAME]), 208 | ok = control_action(Config, delete_user, [?BOB_NAME]), 209 | ok = control_action(Config, delete_user, [?PETER_NAME]). 210 | 211 | init_per_testcase(Testcase, Config) 212 | when Testcase == ldap_and_internal; 213 | Testcase == internal_followed_ldap_and_internal -> 214 | init_internal(Config), 215 | rabbit_ct_helpers:testcase_started(Config, Testcase); 216 | init_per_testcase(Testcase, Config) 217 | when Testcase == tag_attribution_ldap_and_internal; 218 | Testcase == tag_attribution_internal_followed_by_ldap_and_internal -> 219 | % back up tag queries 220 | Cfg = case rabbit_ct_broker_helpers:rpc(Config, 0, 221 | application, 222 | get_env, 223 | [rabbit_auth_backend_ldap, tag_queries]) of 224 | undefined -> undefined; 225 | {ok, X} -> X 226 | end, 227 | rabbit_ct_helpers:set_config(Config, {tag_queries_config, Cfg}), 228 | internal_authorization_teardown(Config), 229 | internal_authorization_setup(Config), 230 | rabbit_ct_helpers:testcase_started(Config, Testcase); 231 | init_per_testcase(Testcase, Config) -> 232 | rabbit_ct_helpers:testcase_started(Config, Testcase). 233 | 234 | end_per_testcase(Testcase, Config) 235 | when Testcase == ldap_and_internal; 236 | Testcase == internal_followed_ldap_and_internal -> 237 | end_internal(Config), 238 | rabbit_ct_helpers:testcase_finished(Config, Testcase); 239 | end_per_testcase(Testcase, Config) 240 | when Testcase == tag_attribution_ldap_and_internal; 241 | Testcase == tag_attribution_internal_followed_by_ldap_and_internal -> 242 | % restore tag queries 243 | Cfg = rabbit_ct_helpers:get_config(Config, tag_queries_config), 244 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 245 | application, 246 | set_env, 247 | [rabbit_auth_backend_ldap, tag_queries, Cfg]), 248 | internal_authorization_teardown(Config), 249 | rabbit_ct_helpers:testcase_finished(Config, Testcase); 250 | end_per_testcase(connections_closed_after_timeout, Config) -> 251 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 252 | application, 253 | set_env, 254 | [rabbitmq_auth_backend_ldap, 255 | other_bind, anon]), 256 | rabbit_ct_helpers:testcase_finished(Config, connections_closed_after_timeout); 257 | end_per_testcase(Testcase, Config) 258 | when Testcase == invalid_or_clause_ldap_only; 259 | Testcase == invalid_and_clause_ldap_only -> 260 | set_env(Config, vhost_access_query_base_env()), 261 | rabbit_ct_helpers:testcase_finished(Config, Testcase); 262 | end_per_testcase(Testcase, Config) 263 | when Testcase == topic_authorisation_publishing_ldap_only; 264 | Testcase == topic_authorisation_consumption -> 265 | set_env(Config, topic_access_query_base_env()), 266 | rabbit_ct_helpers:testcase_finished(Config, Testcase); 267 | end_per_testcase(Testcase, Config) -> 268 | rabbit_ct_helpers:testcase_finished(Config, Testcase). 269 | 270 | 271 | %% ------------------------------------------------------------------- 272 | %% Testsuite cases 273 | %% ------------------------------------------------------------------- 274 | 275 | purge_connection(Config) -> 276 | {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, 277 | rabbit_auth_backend_ldap, 278 | user_login_authentication, 279 | [<>, []]), 280 | 281 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 282 | rabbit_auth_backend_ldap, 283 | purge_connections, []). 284 | 285 | connections_closed_after_timeout(Config) -> 286 | {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, 287 | rabbit_auth_backend_ldap, 288 | user_login_authentication, 289 | [<>, []]), 290 | 291 | [_] = maps:to_list(get_ldap_connections(Config)), 292 | ct:sleep(idle_timeout(with_idle_timeout) + 200), 293 | 294 | %% There should be no connections after idle timeout 295 | [] = maps:to_list(get_ldap_connections(Config)), 296 | 297 | {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, 298 | rabbit_auth_backend_ldap, 299 | user_login_authentication, 300 | [<>, []]), 301 | 302 | ct:sleep(round(idle_timeout(with_idle_timeout)/2)), 303 | 304 | %% Login with password opens different connection, 305 | % so unauthorized connection will be closed 306 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 307 | application, 308 | set_env, 309 | [rabbitmq_auth_backend_ldap, 310 | other_bind, as_user]), 311 | {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, 312 | rabbit_auth_backend_ldap, 313 | user_login_authentication, 314 | [<>, 315 | [{password, <<"password">>}]]), 316 | 317 | ct:sleep(round(idle_timeout(with_idle_timeout)/2)), 318 | 319 | {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, 320 | rabbit_auth_backend_ldap, 321 | user_login_authentication, 322 | [<>, 323 | [{password, <<"password">>}]]), 324 | 325 | ct:sleep(round(idle_timeout(with_idle_timeout)/2)), 326 | 327 | [{Key, _Conn}] = maps:to_list(get_ldap_connections(Config)), 328 | 329 | %% Key will be {IsAnon, Servers, Options} 330 | %% IsAnon is false for password authorization 331 | {false, _, _} = Key. 332 | 333 | 334 | get_ldap_connections(Config) -> 335 | rabbit_ct_broker_helpers:rpc(Config, 0, 336 | rabbit_auth_backend_ldap, get_connections, []). 337 | 338 | ldap_only(Config) -> 339 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 340 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 341 | login(Config), 342 | in_group(Config), 343 | const(Config), 344 | string_match(Config), 345 | boolean_logic(Config), 346 | tag_check(Config, [monitor]), 347 | tag_check_subst(Config), 348 | logging(Config), 349 | ok. 350 | 351 | ldap_and_internal(Config) -> 352 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 353 | application, set_env, [rabbit, auth_backends, 354 | [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]]), 355 | login(Config), 356 | permission_match(Config), 357 | tag_check(Config, [monitor, management, foo]), 358 | ok. 359 | 360 | internal_followed_ldap_and_internal(Config) -> 361 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 362 | application, set_env, [rabbit, auth_backends, 363 | [rabbit_auth_backend_internal, {rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]]), 364 | login(Config), 365 | permission_match(Config), 366 | tag_check(Config, [monitor, management, foo]), 367 | ok. 368 | 369 | tag_attribution_ldap_only(Config) -> 370 | set_env(Config, tag_query_configuration()), 371 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 372 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 373 | tag_check(Config, <<"Edward">>, <<"password">>, [monitor, normal]). 374 | 375 | tag_attribution_ldap_and_internal(Config) -> 376 | set_env(Config, tag_query_configuration()), 377 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 378 | application, set_env, [rabbit, auth_backends, [{rabbit_auth_backend_ldap, 379 | rabbit_auth_backend_internal}]]), 380 | tag_check(Config, <<"Edward">>, <<"password">>, 381 | [monitor, normal] ++ internal_authorization_tags()). 382 | 383 | tag_attribution_internal_followed_by_ldap_and_internal(Config) -> 384 | set_env(Config, tag_query_configuration()), 385 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 386 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_internal, 387 | {rabbit_auth_backend_ldap, 388 | rabbit_auth_backend_internal}]]), 389 | tag_check(Config, <<"Edward">>, <<"password">>, 390 | [monitor, normal] ++ internal_authorization_tags()). 391 | 392 | invalid_or_clause_ldap_only(Config) -> 393 | set_env(Config, vhost_access_query_or_in_group()), 394 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 395 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 396 | B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 397 | {ok, C} = amqp_connection:start(B?ALICE), 398 | ok = amqp_connection:close(C). 399 | 400 | invalid_and_clause_ldap_only(Config) -> 401 | set_env(Config, vhost_access_query_and_in_group()), 402 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 403 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 404 | B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 405 | % NB: if the query crashes the ldap plugin it returns {error, access_refused} 406 | % This may not be a reliable return value assertion 407 | {error, not_allowed} = amqp_connection:start(B?ALICE). 408 | 409 | topic_authorisation_publishing_ldap_only(Config) -> 410 | %% topic authorisation at publishing time is enforced in the AMQP channel 411 | %% so it can be tested by sending messages 412 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 413 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 414 | 415 | %% default is to let pass 416 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 417 | test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), 418 | 419 | %% let pass for topic 420 | set_env(Config, [{topic_access_query, {constant, true}}]), 421 | 422 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 423 | test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), 424 | 425 | %% check string substitution (on username) 426 | set_env(Config, [{topic_access_query, {for, [{permission, write, {equals, "${username}", "Alice"}}, 427 | {permission, read, {constant, false}} 428 | ]}}]), 429 | test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), 430 | test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail), 431 | 432 | %% check string substitution on routing key (with regex) 433 | set_env(Config, [{topic_access_query, {for, [{permission, write, {'and', 434 | [{equals, "${username}", "Alice"}, 435 | {match, {string, "${routing_key}"}, {string, "^a"}}] 436 | }}, 437 | {permission, read, {constant, false}} 438 | ]}}]), 439 | %% user and routing key OK 440 | test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), 441 | %% user and routing key OK 442 | test_publish(P?ALICE, <<"amq.topic">>, <<"a.c">>, ok), 443 | %% user OK, routing key KO, should fail 444 | test_publish(P?ALICE, <<"amq.topic">>, <<"b.c">>, fail), 445 | %% user KO, routing key OK, should fail 446 | test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail), 447 | 448 | ok. 449 | 450 | topic_authorisation_consumption(Config) -> 451 | %% topic authorisation for consumption isn't enforced in AMQP 452 | %% (it is in plugins like STOMP and MQTT, at subscription time) 453 | %% so we directly test the LDAP backend, inside the broker 454 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 455 | ?MODULE, topic_authorisation_consumption1, [Config]). 456 | 457 | topic_authorisation_consumption1(Config) -> 458 | %% we can't use the LDAP backend record here, falling back to simple tuples 459 | Alice = {auth_user,<<"Alice">>, [monitor], 460 | {impl,"cn=Alice,ou=People,dc=rabbitmq,dc=com",<<"password">>} 461 | }, 462 | Bob = {auth_user,<<"Bob">>, [monitor], 463 | {impl,"cn=Bob,ou=People,dc=rabbitmq,dc=com",<<"password">>} 464 | }, 465 | Resource = #resource{virtual_host = <<"/">>, name = <<"amq.topic">>, kind = topic}, 466 | Context = #{routing_key => <<"a.b">>, 467 | variable_map => #{ 468 | <<"username">> => <<"guest">>, 469 | <<"vhost">> => <<"other-vhost">> 470 | }}, 471 | %% default is to let pass 472 | true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), 473 | 474 | %% let pass for topic 475 | set_env(Config, [{topic_access_query, {for, [{permission, read, {constant, true}}, 476 | {permission, write, {constant, false}}] 477 | }}]), 478 | 479 | true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), 480 | 481 | %% check string substitution (on username) 482 | set_env(Config, [{topic_access_query, {for, [{permission, read, {equals, "${username}", "Alice"}}, 483 | {permission, write, {constant, false}}] 484 | }}]), 485 | 486 | true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), 487 | false = rabbit_auth_backend_ldap:check_topic_access(Bob, Resource, read, Context), 488 | 489 | %% check string substitution on routing key (with regex) 490 | set_env(Config, [{topic_access_query, {for, [{permission, read, {'and', 491 | [{equals, "${username}", "Alice"}, 492 | {match, {string, "${routing_key}"}, {string, "^a"}}] 493 | }}, 494 | {permission, write, {constant, false}}] 495 | }}]), 496 | %% user and routing key OK 497 | true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"a.b.c">>}), 498 | %% user and routing key OK 499 | true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"a.c">>}), 500 | %% user OK, routing key KO, should fail 501 | false = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"b.c">>}), 502 | %% user KO, routing key OK, should fail 503 | false = rabbit_auth_backend_ldap:check_topic_access(Bob, Resource, read, #{routing_key => <<"a.b.c">>}), 504 | ok. 505 | 506 | match_bidirectional(Config) -> 507 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 508 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 509 | 510 | Configurations = [ 511 | fun resource_access_query_match/0, 512 | fun resource_access_query_match_query_is_string/0, 513 | fun resource_access_query_match_re_query_is_string/0, 514 | fun resource_access_query_match_query_and_re_query_are_strings/0 515 | ], 516 | 517 | [begin 518 | set_env(Config, ConfigurationFunction()), 519 | Q1 = [#'queue.declare'{queue = <<"Alice-queue">>}], 520 | Q2 = [#'queue.declare'{queue = <<"Ali">>}], 521 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 522 | [test_resource(PTR) || PTR <- [{P?ALICE, Q1, ok}, 523 | {P?ALICE, Q2, fail}]] 524 | end || ConfigurationFunction <- Configurations], 525 | ok. 526 | 527 | match_bidirectional_gh_100(Config) -> 528 | ok = rabbit_ct_broker_helpers:rpc(Config, 0, 529 | application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 530 | 531 | Configurations = [ 532 | fun resource_access_query_match_gh_100/0, 533 | fun resource_access_query_match_query_is_string_gh_100/0 534 | ], 535 | 536 | [begin 537 | set_env(Config, ConfigurationFunction()), 538 | Q1 = [#'queue.declare'{queue = <<"Jimmy-queue">>}], 539 | Q2 = [#'queue.declare'{queue = <<"Jimmy">>}], 540 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 541 | [test_resource(PTR) || PTR <- [{P?JIMMY, Q1, ok}, 542 | {P?JIMMY, Q2, ok}]] 543 | end || ConfigurationFunction <- Configurations], 544 | ok. 545 | 546 | %%-------------------------------------------------------------------- 547 | 548 | test_publish(Person, Exchange, RoutingKey, ExpectedResult) -> 549 | {ok, Connection} = amqp_connection:start(Person), 550 | {ok, Channel} = amqp_connection:open_channel(Connection), 551 | ActualResult = 552 | try 553 | Publish = #'basic.publish'{exchange = Exchange, routing_key = RoutingKey}, 554 | amqp_channel:cast(Channel, Publish, #amqp_msg{payload = <<"foobar">>}), 555 | amqp_channel:call(Channel, #'basic.qos'{prefetch_count = 0}), 556 | ok 557 | catch exit:_ -> fail 558 | after 559 | amqp_connection:close(Connection) 560 | end, 561 | ExpectedResult = ActualResult. 562 | 563 | login(Config) -> 564 | lists:flatten( 565 | [test_login(Config, {N, Env}, L, FilterList, case {LGood, EnvGood} of 566 | {good, good} -> fun succ/1; 567 | _ -> fun fail/1 568 | end) || 569 | {LGood, FilterList, L, _Tags} <- logins(Config), 570 | {N, {EnvGood, Env}} <- login_envs()]). 571 | 572 | logins(Config) -> logins_network(Config) ++ logins_direct(Config). 573 | 574 | %% Format for login tests, {Outcome, FilterList, Login, Tags}. 575 | %% Tests skipped for each login_env reference in FilterList. 576 | logins_network(Config) -> 577 | B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 578 | [{bad, [5, 6], B#amqp_params_network{}, []}, 579 | {bad, [5, 6], B#amqp_params_network{username = <>}, []}, 580 | {bad, [5, 6], B#amqp_params_network{username = <>, 581 | password = <<"password">>}, []}, 582 | {bad, [5, 6], B#amqp_params_network{username = <<"Alice">>, 583 | password = <<"Alicja">>, 584 | virtual_host = <>}, []}, 585 | {bad, [1, 2, 3, 4, 6, 7], B?CAROL, []}, 586 | {good, [5, 6], B?ALICE, []}, 587 | {good, [5, 6], B?BOB, []}, 588 | {good, [1, 2, 3, 4, 6, 7, 8], B?PETER, []}]. 589 | 590 | logins_direct(Config) -> 591 | N = #amqp_params_direct{node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename)}, 592 | [{bad, [5], N#amqp_params_direct{}, []}, 593 | {bad, [5], N#amqp_params_direct{username = <>}, []}, 594 | {bad, [5], N#amqp_params_direct{username = <>, 595 | password = <<"password">>}, [management]}, 596 | {good, [5], N#amqp_params_direct{username = <>, 597 | password = <<"password">>, 598 | virtual_host = <>}, [management]}]. 599 | 600 | %% Format for login envs, {Reference, {Outcome, Env}} 601 | login_envs() -> 602 | [{1, {good, base_login_env()}}, 603 | {2, {good, dn_lookup_pre_bind_env()}}, 604 | {3, {good, other_bind_admin_env()}}, 605 | {4, {good, other_bind_anon_env()}}, 606 | {5, {good, posix_vhost_access_multiattr_env()}}, 607 | {6, {good, tag_queries_subst_env()}}, 608 | {7, {bad, other_bind_broken_env()}}, 609 | {8, {good, vhost_access_query_nested_groups_env()}}]. 610 | 611 | base_login_env() -> 612 | [{user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, 613 | {dn_lookup_attribute, none}, 614 | {dn_lookup_base, none}, 615 | {dn_lookup_bind, as_user}, 616 | {other_bind, as_user}, 617 | {tag_queries, [{monitor, {constant, true}}, 618 | {administrator, {constant, false}}, 619 | {management, {constant, false}}]}, 620 | {vhost_access_query, {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}}, 621 | {log, true}]. 622 | 623 | %% TODO configure OpenLDAP to allow a dn_lookup_post_bind_env() 624 | dn_lookup_pre_bind_env() -> 625 | [{user_dn_pattern, "${username}"}, 626 | {dn_lookup_attribute, "cn"}, 627 | {dn_lookup_base, "OU=People,DC=rabbitmq,DC=com"}, 628 | {dn_lookup_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. 629 | 630 | other_bind_admin_env() -> 631 | [{other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. 632 | 633 | other_bind_anon_env() -> 634 | [{other_bind, anon}]. 635 | 636 | other_bind_broken_env() -> 637 | [{other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admi"}}]. 638 | 639 | tag_queries_subst_env() -> 640 | [{tag_queries, [{administrator, {constant, false}}, 641 | {management, 642 | {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}}]}]. 643 | 644 | posix_vhost_access_multiattr_env() -> 645 | [{user_dn_pattern, "uid=${username},ou=People,dc=rabbitmq,dc=com"}, 646 | {vhost_access_query, 647 | {'and', [{exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}, 648 | {equals, 649 | {attribute, "${user_dn}","memberOf"}, 650 | {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}}, 651 | {equals, 652 | {attribute, "${user_dn}","memberOf"}, 653 | {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}}, 654 | {equals, 655 | {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}, 656 | {attribute,"${user_dn}","memberOf"}}, 657 | {equals, 658 | {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}, 659 | {attribute, "${user_dn}","memberOf"}}, 660 | {match, 661 | {attribute, "${user_dn}","memberOf"}, 662 | {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}}, 663 | {match, 664 | {attribute, "${user_dn}","memberOf"}, 665 | {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}}, 666 | {match, 667 | {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}, 668 | {attribute, "${user_dn}","memberOf"}}, 669 | {match, 670 | {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}, 671 | {attribute, "${user_dn}","memberOf"}} 672 | ]}}]. 673 | 674 | vhost_access_query_or_in_group() -> 675 | [{vhost_access_query, 676 | {'or', [ 677 | {in_group, "cn=bananas,ou=groups,dc=rabbitmq,dc=com"}, 678 | {in_group, "cn=apples,ou=groups,dc=rabbitmq,dc=com"}, 679 | {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} 680 | ]}}]. 681 | 682 | vhost_access_query_and_in_group() -> 683 | [{vhost_access_query, 684 | {'and', [ 685 | {in_group, "cn=bananas,ou=groups,dc=rabbitmq,dc=com"}, 686 | {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} 687 | ]}}]. 688 | 689 | vhost_access_query_nested_groups_env() -> 690 | [{vhost_access_query, {in_group_nested, "cn=admins,ou=groups,dc=rabbitmq,dc=com"}}]. 691 | 692 | vhost_access_query_base_env() -> 693 | [{vhost_access_query, vhost_access_query_base()}]. 694 | 695 | vhost_access_query_base() -> 696 | {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}. 697 | 698 | resource_access_query_match_gh_100() -> 699 | [{resource_access_query, 700 | {match, {string, "RMQ-${vhost}"}, {attribute, "${user_dn}", "description"}} 701 | }]. 702 | 703 | resource_access_query_match_query_is_string_gh_100() -> 704 | [{resource_access_query, 705 | {match, "RMQ-${vhost}", {attribute, "${user_dn}", "description"}} 706 | }]. 707 | 708 | resource_access_query_match() -> 709 | [{resource_access_query, {match, {string, "${name}"}, 710 | {string, "^${username}-"}} 711 | }]. 712 | 713 | resource_access_query_match_query_is_string() -> 714 | [{resource_access_query, {match, "${name}", 715 | {string, "^${username}-"}} 716 | }]. 717 | 718 | resource_access_query_match_re_query_is_string() -> 719 | [{resource_access_query, {match, {string, "${name}"}, 720 | "^${username}-"} 721 | }]. 722 | 723 | resource_access_query_match_query_and_re_query_are_strings() -> 724 | [{resource_access_query, {match, "${name}", 725 | "^${username}-"} 726 | }]. 727 | 728 | topic_access_query_base_env() -> 729 | [{topic_access_query, topic_access_query_base()}]. 730 | 731 | topic_access_query_base() -> 732 | {constant, true}. 733 | 734 | test_login(Config, {N, Env}, Login, FilterList, ResultFun) -> 735 | case lists:member(N, FilterList) of 736 | true -> []; 737 | _ -> 738 | try 739 | set_env(Config, Env), 740 | ResultFun(Login) 741 | after 742 | set_env(Config, base_login_env()) 743 | end 744 | end. 745 | 746 | rpc_set_env(Config, Args) -> 747 | rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, Args). 748 | 749 | set_env(Config, Env) -> 750 | [rpc_set_env(Config, [rabbitmq_auth_backend_ldap, K, V]) || {K, V} <- Env]. 751 | 752 | succ(Login) -> 753 | {ok, Pid} = amqp_connection:start(Login), 754 | amqp_connection:close(Pid). 755 | fail(Login) -> ?assertMatch({error, _}, amqp_connection:start(Login)). 756 | 757 | %%-------------------------------------------------------------------- 758 | 759 | in_group(Config) -> 760 | X = [#'exchange.declare'{exchange = <<"test">>}], 761 | B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 762 | test_resources([{B?ALICE, X, ok}, 763 | {B?BOB, X, fail}]). 764 | 765 | const(Config) -> 766 | Q = [#'queue.declare'{queue = <<"test">>}], 767 | B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 768 | test_resources([{B?ALICE, Q, ok}, 769 | {B?BOB, Q, fail}]). 770 | 771 | string_match(Config) -> 772 | B = fun(N) -> 773 | [#'exchange.declare'{exchange = N}, 774 | #'queue.declare'{queue = <<"test">>}, 775 | #'queue.bind'{exchange = N, queue = <<"test">>}] 776 | end, 777 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 778 | test_resources([{P?ALICE, B(<<"xch-Alice-abc123">>), ok}, 779 | {P?ALICE, B(<<"abc123">>), fail}, 780 | {P?ALICE, B(<<"xch-Someone Else-abc123">>), fail}]). 781 | 782 | boolean_logic(Config) -> 783 | Q1 = [#'queue.declare'{queue = <<"test1">>}, 784 | #'basic.consume'{queue = <<"test1">>}], 785 | Q2 = [#'queue.declare'{queue = <<"test2">>}, 786 | #'basic.consume'{queue = <<"test2">>}], 787 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 788 | [test_resource(PTR) || PTR <- [{P?ALICE, Q1, ok}, 789 | {P?ALICE, Q2, ok}, 790 | {P?BOB, Q1, fail}, 791 | {P?BOB, Q2, fail}]]. 792 | 793 | permission_match(Config) -> 794 | B = fun(N) -> 795 | [#'exchange.declare'{exchange = N}, 796 | #'queue.declare'{queue = <<"prefix-test">>}, 797 | #'queue.bind'{exchange = N, queue = <<"prefix-test">>}] 798 | end, 799 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 800 | test_resources([{P?ALICE, B(<<"prefix-abc123">>), ok}, 801 | {P?ALICE, B(<<"abc123">>), fail}, 802 | {P?ALICE, B(<<"xch-Alice-abc123">>), fail}]). 803 | 804 | %% Tag check tests, with substitution 805 | tag_check_subst(Config) -> 806 | lists:flatten( 807 | [test_tag_check(Config, tag_queries_subst_env(), 808 | fun () -> tag_check(Config, Username, Password, VHost, Outcome, Tags) end) || 809 | {Outcome, _FilterList, #amqp_params_direct{username = Username, 810 | password = Password, 811 | virtual_host = VHost}, 812 | Tags} <- logins_direct(Config)]). 813 | 814 | %% Tag check 815 | tag_check(Config, Tags) -> 816 | tag_check(Config, <>, <<"password">>, Tags). 817 | 818 | tag_check(Config, Username, Password, Tags) -> 819 | tag_check(Config, Username, Password, <<>>, good, Tags). 820 | 821 | tag_check(Config, Username, Password, VHost, Outcome, Tags) 822 | when is_binary(Username), is_binary(Password), is_binary(VHost), is_list(Tags) -> 823 | {ok, User} = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_access_control, check_user_login, [Username, [{password, Password}, {vhost, VHost}]]), 824 | tag_check_outcome(Outcome, Tags, User); 825 | tag_check(_, _, _, _, _, _) -> fun() -> [] end. 826 | 827 | tag_check_outcome(good, Tags, User) -> ?assertEqual(Tags, User#user.tags); 828 | tag_check_outcome(bad, Tags, User) -> ?assertNotEqual(Tags, User#user.tags). 829 | 830 | test_tag_check(Config, Env, TagCheckFun) -> 831 | try 832 | set_env(Config, Env), 833 | TagCheckFun() 834 | after 835 | set_env(Config, base_login_env()) 836 | end. 837 | 838 | tag_query_configuration() -> 839 | [{tag_queries, 840 | [{administrator, {constant, false}}, 841 | %% Query result for tag `management` is FALSE 842 | %% because this object does NOT exist. 843 | {management, 844 | {exists, "cn=${username},ou=Faculty,dc=Computer Science,dc=Engineering"}}, 845 | {monitor, {constant, true}}, 846 | %% Query result for tag `normal` is TRUE because 847 | %% this object exists. 848 | {normal, 849 | {exists, "cn=${username},ou=people,dc=rabbitmq,dc=com"}}]}]. 850 | 851 | internal_authorization_setup(Config) -> 852 | ok = control_action(Config, add_user, ["Edward", ""]), 853 | ok = control_action(Config, set_user_tags, ["Edward"] ++ 854 | [ atom_to_list(T) || T <- internal_authorization_tags() ]). 855 | 856 | internal_authorization_teardown(Config) -> 857 | control_action(Config, delete_user, ["Edward"]). 858 | 859 | internal_authorization_tags() -> 860 | [foo, bar]. 861 | 862 | %% Logging tests, triggered within 'test_login/4' 863 | logging(Config) -> 864 | lists:flatten( 865 | [test_login(Config, {N, Env}, L, FilterList, case {LGood, EnvGood} of 866 | {good, good} -> fun succ/1; 867 | _ -> fun fail/1 868 | end) || 869 | {LGood, FilterList, L} <- logging_test_users(Config), 870 | {N, {EnvGood, Env}} <- logging_envs()]). 871 | 872 | %% Format for logging tests, {Outcome, FilterList, Login}. 873 | logging_test_users(Config) -> 874 | P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 875 | [{bad, [], P#amqp_params_network{username = <>}}, 876 | {good, [], P?ALICE}]. 877 | 878 | logging_envs() -> 879 | [{1, {good, scrub_bind_creds_env()}}, 880 | {2, {good, display_bind_creds_env()}}, 881 | {3, {bad, scrub_bind_single_cred_env()}}, 882 | {4, {bad, scrub_bind_creds_no_equals_env()}}, 883 | {5, {bad, scrub_bind_creds_no_seperator_env()}}]. 884 | 885 | scrub_bind_creds_env() -> 886 | [{log, network}, 887 | {other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. 888 | 889 | display_bind_creds_env() -> 890 | [{log, network_unsafe}, 891 | {other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. 892 | 893 | scrub_bind_single_cred_env() -> 894 | [{log, network}, 895 | {other_bind, {"dc=com", "admin"}}]. 896 | 897 | scrub_bind_creds_no_equals_env() -> 898 | [{log, network}, 899 | {other_bind, {"cn*admin,dc>rabbitmq,dc&com", "admin"}}]. 900 | 901 | scrub_bind_creds_no_seperator_env() -> 902 | [{log, network}, 903 | {other_bind, {"cn=admindc=rabbitmqdc&com", "admin"}}]. 904 | 905 | %%-------------------------------------------------------------------- 906 | 907 | test_resources(PTRs) -> [test_resource(PTR) || PTR <- PTRs]. 908 | 909 | test_resource({Person, Things, Result}) -> 910 | {ok, Conn} = amqp_connection:start(Person), 911 | {ok, Ch} = amqp_connection:open_channel(Conn), 912 | ?assertEqual(Result, 913 | try 914 | [amqp_channel:call(Ch, T) || T <- Things], 915 | ok 916 | catch exit:_ -> fail 917 | after 918 | amqp_connection:close(Conn) 919 | end). 920 | 921 | control_action(Config, Command, Args) -> 922 | Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), 923 | control_action(Config, Command, Node, Args, default_options()). 924 | 925 | control_action(Config, Command, Args, NewOpts) -> 926 | Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), 927 | control_action(Config, Command, Node, Args, 928 | expand_options(default_options(), NewOpts)). 929 | 930 | control_action(_Config, Command, Node, Args, Opts) -> 931 | case rabbit_control_helper:command(Command, Node, Args, Opts) of 932 | ok -> 933 | io:format("done.~n"), 934 | ok; 935 | Other -> 936 | io:format("failed.~n"), 937 | Other 938 | end. 939 | 940 | default_options() -> [{"-p", ?VHOST}, {"-q", "false"}]. 941 | 942 | expand_options(As, Bs) -> 943 | lists:foldl(fun({K, _}=A, R) -> 944 | case proplists:is_defined(K, R) of 945 | true -> R; 946 | false -> [A | R] 947 | end 948 | end, Bs, As). 949 | 950 | -------------------------------------------------------------------------------- /test/system_SUITE_data/init-slapd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # vim:sw=4:et: 3 | 4 | set -ex 5 | 6 | slapd_data_dir=$1 7 | tcp_port=$2 8 | 9 | pidfile="$slapd_data_dir/slapd.pid" 10 | uri="ldap://localhost:$tcp_port" 11 | 12 | binddn="cn=config" 13 | passwd=secret 14 | 15 | case "$(uname -s)" in 16 | Linux) 17 | slapd=/usr/sbin/slapd 18 | modulepath=/usr/lib/ldap 19 | schema_dir=/etc/ldap/schema 20 | ;; 21 | FreeBSD) 22 | slapd=/usr/local/libexec/slapd 23 | modulepath=/usr/local/libexec/openldap 24 | schema_dir=/usr/local/etc/openldap/schema 25 | ;; 26 | *) 27 | exit 1 28 | ;; 29 | esac 30 | 31 | # -------------------------------------------------------------------- 32 | # slapd(8) configuration + start 33 | # -------------------------------------------------------------------- 34 | 35 | rm -rf "$slapd_data_dir" 36 | mkdir -p "$slapd_data_dir" 37 | 38 | conf_file=$slapd_data_dir/slapd.conf 39 | cat < "$conf_file" 40 | include $schema_dir/core.schema 41 | include $schema_dir/cosine.schema 42 | include $schema_dir/nis.schema 43 | include $schema_dir/inetorgperson.schema 44 | pidfile $pidfile 45 | modulepath $modulepath 46 | loglevel 7 47 | 48 | database config 49 | rootdn "$binddn" 50 | rootpw $passwd 51 | EOF 52 | 53 | cat "$conf_file" 54 | 55 | conf_dir=$slapd_data_dir/slapd.d 56 | mkdir -p "$conf_dir" 57 | 58 | # Start slapd(8). 59 | "$slapd" \ 60 | -f "$conf_file" \ 61 | -F "$conf_dir" \ 62 | -h "$uri" 63 | 64 | auth="-x -D $binddn -w $passwd" 65 | 66 | # We wait for the server to start. 67 | for seconds in 1 2 3 4 5 6 7 8 9 10; do 68 | ldapsearch $auth -H "$uri" -LLL -b cn=config dn && break; 69 | sleep 1 70 | done 71 | 72 | # -------------------------------------------------------------------- 73 | # Load the example LDIFs for the testsuite. 74 | # -------------------------------------------------------------------- 75 | 76 | script_dir=$(cd "$(dirname "$0")" && pwd) 77 | example_ldif_dir="$script_dir/../../example" 78 | example_data_dir="$slapd_data_dir/example" 79 | mkdir -p "$example_data_dir" 80 | 81 | # We update the hard-coded database directory with the one we computed 82 | # here, so the data is located inside the test directory. 83 | sed -E -e "s,^olcDbDirectory:.*,olcDbDirectory: $example_data_dir," \ 84 | < "$example_ldif_dir/global.ldif" | \ 85 | ldapadd $auth -H "$uri" 86 | 87 | # We remove the module path from the example LDIF as it was already 88 | # configured. 89 | sed -E -e "s,^olcModulePath:.*,olcModulePath: $modulepath," \ 90 | < "$example_ldif_dir/memberof_init.ldif" | \ 91 | ldapadd $auth -H "$uri" 92 | 93 | ldapmodify $auth -H "$uri" -f "$example_ldif_dir/refint_1.ldif" 94 | ldapadd $auth -H "$uri" -f "$example_ldif_dir/refint_2.ldif" 95 | 96 | ldapsearch $auth -H "$uri" -LLL -b cn=config dn 97 | 98 | echo SLAPD_PID=$(cat "$pidfile") 99 | -------------------------------------------------------------------------------- /test/unit_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% This Source Code Form is subject to the terms of the Mozilla Public 2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this 3 | %% file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | %% 5 | %% Copyright (c) 2007-2020 VMware, Inc. or its affiliates. All rights reserved. 6 | %% 7 | 8 | -module(unit_SUITE). 9 | 10 | -include_lib("common_test/include/ct.hrl"). 11 | -include_lib("eunit/include/eunit.hrl"). 12 | 13 | -compile([export_all]). 14 | 15 | all() -> 16 | [ 17 | fill, 18 | ad_fill 19 | ]. 20 | 21 | fill(_Config) -> 22 | F = fun(Fmt, Args, Res) -> 23 | ?assertEqual(Res, rabbit_auth_backend_ldap_util:fill(Fmt, Args)) 24 | end, 25 | F("x${username}x", [{username, "ab"}], "xabx"), 26 | F("x${username}x", [{username, ab}], "xabx"), 27 | F("x${username}x", [{username, <<"ab">>}], "xabx"), 28 | F("x${username}x", [{username, ""}], "xx"), 29 | F("x${username}x", [{fusername, "ab"}], "x${username}x"), 30 | F("x${usernamex", [{username, "ab"}], "x${usernamex"), 31 | F("x${username}x", [{username, "a\\b"}], "xa\\bx"), 32 | F("x${username}x", [{username, "a&b"}], "xa&bx"), 33 | ok. 34 | 35 | ad_fill(_Config) -> 36 | F = fun(Fmt, Args, Res) -> 37 | ?assertEqual(Res, rabbit_auth_backend_ldap_util:fill(Fmt, Args)) 38 | end, 39 | 40 | U0 = <<"ADDomain\\ADUser">>, 41 | A0 = rabbit_auth_backend_ldap_util:get_active_directory_args(U0), 42 | F("x-${ad_domain}-x-${ad_user}-x", A0, "x-ADDomain-x-ADUser-x"), 43 | 44 | U1 = <<"ADDomain\\ADUser\\Extra">>, 45 | A1 = rabbit_auth_backend_ldap_util:get_active_directory_args(U1), 46 | F("x-${ad_domain}-x-${ad_user}-x", A1, "x-ADDomain-x-ADUser\\Extra-x"), 47 | ok. 48 | --------------------------------------------------------------------------------