├── .gitignore ├── BUG_REPORT.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEPENDENCIES.md ├── DEVELOPMENT.md ├── ISSUE.md ├── LICENCE ├── OWNERS ├── PULL_REQUEST.md ├── README.md ├── kafka-consumer-example ├── .gitignore ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── bfm │ │ └── kafka │ │ ├── ConsumerApp.java │ │ ├── ConsumerCreator.java │ │ ├── CustomObject.java │ │ └── IKafkaConstants.java │ └── resources │ ├── log4j2.xml │ └── oauth-configuration.properties ├── kafka-oauth ├── .gitignore ├── KeyCloak-Setup.md ├── README.md ├── pictures │ └── KeyCloak │ │ ├── Add-ClientScopes.png │ │ ├── Admin-ServiceAccountRoles.png │ │ ├── AdminClient.png │ │ ├── TestBroker-ClientScopes.png │ │ ├── TestBroker.png │ │ ├── TestConsumer-ClientScopes.png │ │ ├── TestConsumer.png │ │ ├── TestProducer-ClientScopes.png │ │ ├── TestProducer.png │ │ └── standalone-boot-files.png ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── bfm │ │ │ └── kafka │ │ │ └── security │ │ │ └── oauthbearer │ │ │ ├── CustomAuthorizer.java │ │ │ ├── CustomPrincipal.java │ │ │ ├── CustomPrincipalBuilder.java │ │ │ ├── EnvironmentVariablesUtil.java │ │ │ ├── OAuthAuthenticateCallbackHandler.java │ │ │ ├── OAuthAuthenticateLoginCallbackHandler.java │ │ │ ├── OAuthAuthenticateValidatorCallbackHandler.java │ │ │ ├── OAuthBearerTokenJwt.java │ │ │ ├── OAuthConfiguration.java │ │ │ ├── OAuthScope.java │ │ │ ├── OAuthService.java │ │ │ ├── OAuthServiceImpl.java │ │ │ └── Utils.java │ └── resources │ │ ├── META-INF │ │ └── MANIFEST.MF │ │ └── oauth-configuration.properties │ └── test │ ├── java │ └── com │ │ └── bfm │ │ └── kafka │ │ └── security │ │ └── oauthbearer │ │ ├── CustomAuthorizerTest.java │ │ ├── CustomPrincipalBuilderTest.java │ │ ├── CustomPrincipalTest.java │ │ ├── EnvironmentVariablesUtilTest.java │ │ ├── OAuthAuthenticateCallbackHandlerTest.java │ │ ├── OAuthAuthenticateLoginCallbackHandlerTest.java │ │ ├── OAuthAuthenticateValidatorCallbackHandlerTest.java │ │ ├── OAuthBearerTokenJwtTests.java │ │ ├── OAuthConfigurationTests.java │ │ ├── OAuthScopeTest.java │ │ ├── OAuthServiceImplTests.java │ │ └── UtilsTest.java │ └── resources │ ├── log4j2.xml │ └── oauth-configuration.properties ├── kafka-producer-example ├── .gitignore ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── bfm │ │ └── kafka │ │ ├── CustomObject.java │ │ ├── IKafkaConstants.java │ │ ├── ProducerApp.java │ │ └── ProducerCreator.java │ └── resources │ ├── log4j2.xml │ └── oauth-configuration.properties └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | 18 | 19 | # Project 20 | target/ 21 | /null/ 22 | /.checkstyle 23 | .eclipse-pmd 24 | .settings/org.springframework.ide.eclipse.prefs 25 | git.properties 26 | /logs/ 27 | /${sys:myserver.writable.root.dir}/ 28 | -------------------------------------------------------------------------------- /BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | If you are reporting a problem, please make sure the following information are provided: 7 | 8 | **Expected behavior and actual behavior:** 9 | A clear and concise description of what you expected to happen and what's the actual behavior. If applicable, add screenshots to help explain your problem. 10 | 11 | **Steps to reproduce the problem:** 12 | Please provide the steps to reproduce this problem. 13 | 14 | **Versions:** 15 | Please specify the versions of following systems. 16 | 17 | - (your thing) version: [x.x.x] 18 | - (related thing - docker, K8s, browser, ...) version: [y.y.y] 19 | 20 | **Additional context:** 21 | 22 | > Examples only 23 | 24 | - **some random output or error:** You can get them `here`. 25 | - **Some Log files:** You can get them `here`. 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | # [1.0.0.0] (01-22-2020) 5 | 6 | ### Features 7 | 1.0.0 Initial version of lib-kafka-oauth with KeyCloak OAuth Server 8 | 9 | 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Code of Conduct 2 | 3 | We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to the Lib-Kafka-OAuth project! 4 | 5 | ## Getting Started 6 | 7 | ------ 8 | 9 | If you have questions, comments, or requests feel free to create an issue on GitHub. 10 | 11 | If you want to contribute code and you are new to the Java programming language, check out 12 | the [DEVELOPMENT.md](./DEVELOPMENT.md) reference for help getting started. 13 | 14 | We currently welcome contributions of all kinds. For example: 15 | 16 | - Development of features, bug fixes, and other improvements. 17 | - Documentation including reference material and examples. 18 | - Bug and feature reports. 19 | 20 | ## Contribution process 21 | 22 | ------ 23 | 24 | Small bug fixes (or other small improvements) can be submitted directly via a Pull Request on GitHub. 25 | You can expect at least one of the Lib-Kafka-OAuth maintainers to respond quickly. 26 | 27 | Before submitting large changes, please open an issue on GitHub outlining: 28 | 29 | - The use case that your changes are applicable to. 30 | - Steps to reproduce the issue(s) if applicable. 31 | - Detailed description of what your changes would entail. 32 | - Alternative solutions or approaches if applicable. 33 | 34 | Use your judgement about what constitutes a large change. If you aren't sure submit an issue on GitHub. 35 | 36 | ### Code Contributions 37 | 38 | ------ 39 | 40 | If you are contributing code, please consider the following: 41 | 42 | - Most changes should be accompanied with tests. 43 | - All commits must be signed off (see next section). 44 | - Related commits must be squashed before they are merged. 45 | - All tests must pass. 46 | 47 | Avoid adding third-party dependencies (_vendoring_). Lib-Kafka-OAuth is designed to be minimal, 48 | lightweight, and easily embedded. **Vendoring** may make features _easier_ to 49 | implement however they come with their own cost for both Lib-Kafka-OAuth developers and 50 | Lib-Kafka-OAuth users (e.g., _vendoring_ conflicts, security, debugging, etc.) 51 | 52 | ### Commit Messages 53 | 54 | ------ 55 | 56 | Commit messages should explain *why* the changes were made and should follow The Conventional Commits spec below. The specification is a lightweight convention on top of commit messages. It provides an _easy set of rules_ for creating an explicit commit history; which makes it *easier to write automated tools* on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages. 57 | 58 | The commit message should be structured as follows: 59 | 60 | ```bash 61 | [optional scope]: 62 | 63 | [optional body] 64 | 65 | [optional footer(s)] 66 | ``` 67 | 68 | The commit contains the following structural elements, to communicate intent to the consumers of your library: 69 | 70 | 1. **fix:** a commit of the type fix patches a bug in your codebase (this correlates with [PATCH](http://semver.org/#summary) in semantic versioning). 71 | 72 | 2. **feat:** a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in semantic versioning). 73 | 74 | 3. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:`, or appends a `!` after the _type/scope_, introduces a breaking API change (correlating with MAJOR in semantic versioning). A `BREAKING CHANGE` can be part of commits of any type. 75 | 76 | 4. **types** other than `fix:` and `feat:` are allowed, for example `@commitlint/config-conventional` recommends `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others. 77 | 78 | 5. **footers** other than `BREAKING CHANGE: ` may be provided and follow a convention similar to [git trailer format](https://git-scm.com/docs/git-interpret-trailers). 79 | 80 | If your changes are related to an **open issue** (bug or feature), please include the following line at the end of your commit message: 81 | 82 | ```bash 83 | Last Line: Fixes # 84 | ``` 85 | 86 | Additional types are not mandated by the conventional commits specification, and have no implicit effect in semantic versioning (unless they include a BREAKING CHANGE). 87 | 88 | A scope may be provided to a commits `type`, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays.` 89 | 90 | > Why Use Conventional Commits? 91 | 92 | 1. Auto generating CHANGELOGs. 93 | 2. Automatically determines a `semantic version` bump (based on the types of commits landed). 94 | 3. Communicate the nature of changes to teammates, the community, and other stakeholders. 95 | 4. Triggering build and publish processes. 96 | 5. Makes it easier for people to contribute to your projects, by allowing them to explore a more structured commit history. 97 | 98 | #### Developer Certificate Of Origin 99 | 100 | ------ 101 | 102 | The Lib-Kafka-OAuth project requires that contributors sign off on changes submitted to Lib-Kafka-OAuth repositories. 103 | The [Developer Certificate of Origin (DCO)](https://developercertificate.org/) is a simple way to certify that you wrote or have the right to submit the code you are contributing to the project. 104 | 105 | The `DCO` is a standard requirement for `Linux Foundation` and `CNCF projects`. 106 | 107 | You _sign-off_ by adding the following to the footer of your commit message manually: 108 | 109 | ```bash 110 | feat: some new feature 111 | 112 | ... 113 | Signed-off-by: Random J Developer 114 | ``` 115 | 116 | Moreover, Git has a `-s, --signoff` commit sub-command option to add `add Signed-off-by:` automatically. 117 | 118 | ```bash 119 | git commit -s -m 'feat: some new feature' 120 | ``` 121 | 122 | #### More Examples 123 | 124 | ------ 125 | 126 | Commit message with description and breaking change footer. 127 | 128 | ```bash 129 | feat: allow provided config object to extend other configs 130 | 131 | BREAKING CHANGE: `extends` key in config file is now used for extending other config files 132 | ``` 133 | 134 | Commit message with `!` to draw attention to breaking change. 135 | 136 | ```bash 137 | refactor!: drop support for Node 8 138 | ``` 139 | 140 | Commit message with both `!` and BREAKING CHANGE footer. 141 | 142 | ```bash 143 | refactor!: drop support for Node 8 144 | 145 | BREAKING CHANGE: refactor to use JavaScript features not available in Node 8. 146 | ``` 147 | 148 | Commit message with no body. 149 | 150 | ```bash 151 | docs: correct spelling of CHANGELOG 152 | ``` 153 | 154 | Commit message with scope. 155 | 156 | ```bash 157 | feat(lang): add polish language 158 | ``` 159 | 160 | Commit message with multi-paragraph body and multiple footers. 161 | 162 | ```bash 163 | fix: correct minor typos in code 164 | 165 | see the issue for details on typos fixed. 166 | 167 | follows on additional documentation updates as part of refs below. 168 | 169 | Reviewed-by: Z 170 | Signed-off-by: Random J Developer 171 | Refs #133 172 | Fixes #244 173 | ``` 174 | 175 | You can find the full text of the DCO [here](https://developercertificate.org/) 176 | 177 | ### Code Review 178 | 179 | ------ 180 | 181 | Before a Pull Request is merged, it will undergo code review from other members 182 | of the Lib-Kafka-OAuth community. In order to streamline the code review process, when 183 | amending your Pull Request in response to a review, do not squash your changes 184 | into relevant commits until it has been approved for merge. This allows the 185 | reviewer to see what changes are new and removes the need to wade through code 186 | that has not been modified to search for a small change. 187 | 188 | When adding temporary patches in response to review comments, consider 189 | formatting the message description like one of the following: 190 | 191 | - `chore: fixup into commit (squash before merge)` 192 | - `chore: fixed changes requested by @username (squash before merge)` 193 | - `chore: amended (squash before merge)` 194 | 195 | The purpose of these formats is to provide some context into the reason the 196 | temporary commit exists, and to label it as needing squashed before a merge 197 | is performed. 198 | 199 | It is worth noting that not all changes need be squashed before a merge is 200 | performed. Some changes made as a result of review stand well on their own, 201 | independent of other commits in the series. Such changes should be made into 202 | their own commit and added to the PR. 203 | 204 | If your Pull Request is small though, it is acceptable to squash changes during 205 | the review process. Use your judgement about what constitutes a _small_ `Pull 206 | Request`. If you aren't sure, post a comment on the Pull Request. 207 | -------------------------------------------------------------------------------- /DEPENDENCIES.md: -------------------------------------------------------------------------------- 1 | ## Third Party Dependencies & Licenses 2 | 3 | | Name | License 4 | |-----------------------------------------------|-------------------------| 5 | | junit/junit | EPL 1.0 | 6 | | org.apache.kafka/kafka_2.12 | Apache-2.0 | 7 | | javax.validation/validation-api | Apache-2.0 | 8 | | org.apache.logging.log4j/log4j-api | Apache-2.0 | 9 | | org.apache.logging.log4j/log4j-core | Apache-2.0 | 10 | | org.apache.logging.log4j/log4j-slf4j-impl | Apache-2.0 | 11 | | org.mockito/mockito-core | MIT | 12 | | org.powermock/powermock-reflect | Apache-2.0 | 13 | | com.fasterxml.jackson.core/jackson-databind | Apache-2.0 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | Lib-Kafka-OAuth is written in the [Java](https://docs.oracle.com/javase/8/docs/technotes/guides/language/) programming language. 4 | 5 | If you are not familiar with Java we recommend you read through the Java Tutorials (https://docs.oracle.com/javase/tutorial/index.html) to familiarize yourself with the standard Java development environment. 6 | 7 | Requirements: 8 | 9 | - Git 10 | - GitHub account (if you are contributing) 11 | - Java Version 8 12 | - Maven 13 | 14 | ## Getting Started 15 | 16 | After cloning the repository, just run `mvn test`. This will: 17 | 18 | - Install required dependencies. 19 | - Build the Lib-Kafka-OAuth jar. 20 | - Run all of the tests. 21 | 22 | ## Workflow 23 | 24 | 1. Go to [https://github.com//foo](https://github.com/foo) and fork the repository 25 | into your account by clicking the "Fork" button. 26 | 27 | 1. Clone the fork to your local machine. 28 | 29 | ```bash 30 | git clone git@github.com//foo.git foo 31 | cd foo 32 | git remote add upstream https://github.com/foo.git 33 | ``` 34 | 35 | 2. Create a branch for your changes. 36 | 37 | ``` 38 | git checkout -b somefeature 39 | ``` 40 | 41 | 3. Update your local branch with upstream. 42 | 43 | ``` 44 | git fetch upstream 45 | git rebase upstream/master 46 | ``` 47 | 48 | 4. Develop your changes and regularly update your local branch against upstream. 49 | 50 | 51 | 5. Commit changes and push to your fork. 52 | 53 | ``` 54 | git commit -s 55 | git push origin somefeature 56 | ``` 57 | 58 | 6. Submit a Pull Request via https://github.com/\/foo. You 59 | should be prompted to with a "Compare and Pull Request" button that 60 | mentions your branch. 61 | 62 | 7. Once your Pull Request has been reviewed and signed off please squash your 63 | commits. If you have a specific reason to leave multiple commits in the 64 | Pull Request, please mention it in the discussion. 65 | 66 | > If you are not familiar with squashing commits, see [the following blog post for a good overview](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html). 67 | -------------------------------------------------------------------------------- /ISSUE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue/Question 3 | about: Talk/ask about other related items 4 | 5 | --- 6 | 7 | **What can we help you with?** 8 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Adobe 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @shumphre @SamRussak -------------------------------------------------------------------------------- /PULL_REQUEST.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Description 4 | 5 | 6 | 7 | ## Related Issue 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Motivation and Context 15 | 16 | 17 | 18 | ## How Has This Change Been Tested? 19 | 20 | 21 | 22 | 23 | 24 | ## Screenshots (if appropriate) 25 | 26 | ## Types of changes 27 | 28 | 29 | 30 | - [ ] Bug fix (non-breaking change which fixes an issue) 31 | - [ ] New feature (non-breaking change which adds functionality) 32 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 33 | 34 | ## Checklist 35 | 36 | 37 | 38 | 39 | - [ ] I have signed the [Open Source CLA](https://some-cla.html). (If applicable) 40 | - [ ] My code follows the code style of this project. 41 | - [ ] My change requires a change to the documentation. 42 | - [ ] I have updated the documentation accordingly. 43 | - [ ] I have read the **CONTRIBUTING** document. 44 | - [ ] I have added tests to cover my changes. 45 | - [ ] All new and existing tests passed. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lib-Kafka-OAuth 2 | 3 | [logo]: https://https://avatars3.githubusercontent.com/u/64902484?s=400&u=1dcba824aa6a9860b7e40d5e1ebe0e9c888f9bfc&v=4 4 | 5 | The purpose of the library is to improve the out of box Kafka security features, by providing concrete OAuth2 implementation of authentication and authorization for Kafka based on Kafka OAuth Security Framework. 6 | The library utilizes KeyCloak as the OAuth server, but other OAuth servers could be used. 7 | 8 | Learn more [here](https://medium.com/blackrock-engineering/utilizing-oauth-for-kafka-security-5c1da9f3d3d). 9 | 10 | ## Goals 11 | 12 | - Modern industry standard – ability to expire/refresh credentials for long running processes without downtime 13 | - Enterprise grade – support of centralized management and segregation of duties in operation 14 | - DX focused – capability of deployment time configuration 15 | 16 | ## Non-Goals 17 | 18 | - Not to support other Kafka security frameworks or plugins. 19 | 20 | ### Installation 21 | 22 | Prerequisite 23 | - Install and configure JDK Version 8 24 | - Install and configure Maven 25 | - Install and configure Git Client 26 | 27 | Instructions for how to download/install the code onto your machine. 28 | 29 | Clone repository to your local machine: 30 | ```bash 31 | git clone '{REPO_URL}' 32 | ``` 33 | Install project dependencies 34 | ```bash 35 | mvn install 36 | ``` 37 | - Compile the project 38 | ```bash 39 | mvn compile 40 | ``` 41 | 42 | ### Usage 43 | 44 | - It is recommended that you follow the guides in the order outlined below. 45 | - Prerequisites 46 | - Port 8080 open for local KeyCloak 47 | 48 | 1) [KeyCloak Configuration](./kafka-oauth/KeyCloak-Setup.md) 49 | 2) [Kafka OAuth Configuration](./kafka-oauth/README.md) 50 | 3) [Test Consumer Configuration](./kafka-consumer-example/README.md) 51 | 4) [Test Producer Configuration](./kafka-producer-example/README.md) 52 | 53 | ### Contributing 54 | 55 | Contributions are welcomed! Read the [Contributing Guide](./CONTRIBUTING.md) for more information. 56 | 57 | ### Contact Us 58 | 59 | - Create a GitHub [issue](https://github.com/kafka-security/oauth/issues/new) 60 | 61 | ### Licensing 62 | 63 | See [LICENSE](https://github.com/kafka-security/oauth/blob/master/LICENCE) for more information. 64 | 65 | Copyright © 2020 BlackRock Inc. 66 | 67 | Licensed under the Apache License, Version 2.0 (the "License"); 68 | you may not use this file except in compliance with the License. 69 | You may obtain a copy of the License at 70 | 71 | http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software 74 | distributed under the License is distributed on an "AS IS" BASIS, 75 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 76 | See the License for the specific language governing permissions and 77 | limitations under the License. 78 | 79 | ### Credits 80 | 81 | This project was inspired by: 82 | - https://github.com/jairsjunior/kafka-oauth 83 | -------------------------------------------------------------------------------- /kafka-consumer-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | 18 | 19 | # Project 20 | target/ 21 | /null/ 22 | /.checkstyle 23 | .eclipse-pmd 24 | .settings/org.springframework.ide.eclipse.prefs 25 | git.properties 26 | /logs/ 27 | /${sys:myserver.writable.root.dir}/ 28 | -------------------------------------------------------------------------------- /kafka-consumer-example/README.md: -------------------------------------------------------------------------------- 1 | Copyright © 2020 BlackRock Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | #Consumer example using the Kafka OAuth library 16 | 17 | #### Add libkafka.oauthbearer dependency in your `pom.xml` file 18 | 19 | 20 | brs 21 | libkafka.oauthbearer 22 | 1.0.0 23 | 24 | 25 | 26 | #### Add the OAuth configuration to your Consumer 27 | // OAuth Settings 28 | // - sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required; 29 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"); 30 | 31 | // - security.protocol=SASL_PLAINTEXT 32 | props.put("security.protocol", "SASL_PLAINTEXT"); 33 | 34 | // - sasl.mechanism=OAUTHBEARER 35 | props.put("sasl.mechanism", "OAUTHBEARER"); 36 | 37 | // - sasl.login.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler 38 | props.put("sasl.login.callback.handler.class", "com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler"); 39 | 40 | 41 | #### Build the JAR 42 | - If you are running inside a IDE you can skip this step 43 | 44 | #### Config Files 45 | 46 | - Create a properties file for your Consumer OAuth client {oauth-configuration.properties}. 47 | - There is one already in your resource folder! 48 | - The file should contain the following properties: 49 | - NOTE: you will need to change oauth.server.client.secret to be your client secret 50 | 51 | 52 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 53 | oauth.server.token.endpoint.path=/token 54 | oauth.server.introspection.endpoint.path=/token/introspect 55 | oauth.server.client.id=test-consumer 56 | oauth.server.client.secret=4ce54cfb-f359-4400-bd8e-810a1af10f71 57 | oauth.server.grant.type=client_credentials 58 | oauth.server.scopes=test 59 | oauth.server.accept.unsecure.server=true 60 | # oauth.server.accept.unsecure.server - this propertie is for SSL configuration, if you are using HTTP or a self-signed CERT set this true 61 | 62 | 63 | 64 | 65 | #### Configure your ENV Variables 66 | 67 | set KAFKA_OAUTH_SERVER_PROP_FILE={PATH_TO_PROJECT}\kafka-consumer-example\src\main\resources\oauth-configuration.properties 68 | 69 | 70 | #### Run the Consumer 71 | java -jar kafka-consumer-example/target/kakfa-oauth-consumer-example-0.0.1-SNAPSHOT-jar-with-dependencies.jar 72 | 73 | OR 74 | 75 | Run the main Java class from your IDE 76 | -------------------------------------------------------------------------------- /kafka-consumer-example/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | brs 6 | kafka-security 7 | 1.0-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | com.bfm.kafka 12 | kakfa-oauth-consumer-example 13 | 0.0.1-SNAPSHOT 14 | jar 15 | kakfa-oauth-consumer-example 16 | http://maven.apache.org 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | org.apache.kafka 25 | kafka_2.12 26 | 2.2.0 27 | 28 | 29 | 30 | com.fasterxml.jackson.core 31 | jackson-databind 32 | 2.5.0 33 | 34 | 35 | 36 | org.apache.logging.log4j 37 | log4j-api 38 | 2.7 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-core 43 | 2.7 44 | 45 | 46 | org.apache.logging.log4j 47 | log4j-slf4j-impl 48 | 2.7 49 | 50 | 51 | 52 | brs 53 | libkafka.oauthbearer 54 | 1.0.0 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 65 | 8 66 | 8 67 | 68 | 3.8.1 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-assembly-plugin 73 | 74 | 75 | package 76 | 77 | single 78 | 79 | 80 | 81 | 82 | 83 | com.bfm.kafka.ConsumerApp 84 | 85 | 86 | 87 | 88 | jar-with-dependencies 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/java/com/bfm/kafka/ConsumerApp.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import org.apache.kafka.clients.consumer.Consumer; 19 | import org.apache.kafka.clients.consumer.ConsumerRecords; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | public class ConsumerApp { 24 | 25 | private final static Logger log = LoggerFactory.getLogger(ConsumerApp.class); 26 | 27 | public static void main(String[] args) { 28 | runConsumer(); 29 | } 30 | 31 | static void runConsumer() { 32 | Consumer consumer = ConsumerCreator.createConsumer(); 33 | 34 | int noMessageToFetch = 0; 35 | 36 | while (true) { 37 | final ConsumerRecords consumerRecords = consumer.poll(1000); 38 | if (consumerRecords.count() == 0) { 39 | noMessageToFetch++; 40 | if (noMessageToFetch > IKafkaConstants.MAX_NO_MESSAGE_FOUND_COUNT) 41 | break; 42 | else 43 | continue; 44 | } 45 | 46 | consumerRecords.forEach(record -> { 47 | System.out.println("Record Key " + record.key()); 48 | System.out.println("Record value " + record.value()); 49 | System.out.println("Record partition " + record.partition()); 50 | System.out.println("Record offset " + record.offset()); 51 | }); 52 | consumer.commitAsync(); 53 | } 54 | consumer.close(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/java/com/bfm/kafka/ConsumerCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import org.apache.kafka.clients.consumer.Consumer; 19 | import org.apache.kafka.clients.consumer.ConsumerConfig; 20 | import org.apache.kafka.clients.consumer.KafkaConsumer; 21 | import org.apache.kafka.common.serialization.LongDeserializer; 22 | import org.apache.kafka.common.serialization.StringDeserializer; 23 | 24 | import java.util.Collections; 25 | import java.util.Properties; 26 | 27 | public class ConsumerCreator { 28 | 29 | public static Consumer createConsumer() { 30 | final Properties props = new Properties(); 31 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, IKafkaConstants.KAFKA_BROKERS); 32 | props.put(ConsumerConfig.CLIENT_ID_CONFIG, IKafkaConstants.CLIENT_ID); 33 | props.put(ConsumerConfig.GROUP_ID_CONFIG, IKafkaConstants.GROUP_ID_CONFIG); 34 | props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class.getName()); 35 | props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); 36 | props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, IKafkaConstants.MAX_POLL_RECORDS); 37 | props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); 38 | props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, IKafkaConstants.OFFSET_RESET_EARLIER); 39 | 40 | // OAuth Settings 41 | // - sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required; 42 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"); 43 | 44 | // - security.protocol=SASL_PLAINTEXT 45 | props.put("security.protocol", "SASL_PLAINTEXT"); 46 | 47 | // - sasl.mechanism=OAUTHBEARER 48 | props.put("sasl.mechanism", "OAUTHBEARER"); 49 | 50 | // - sasl.login.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler 51 | props.put("sasl.login.callback.handler.class", "com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler"); 52 | 53 | final Consumer consumer = new KafkaConsumer<>(props); 54 | consumer.subscribe(Collections.singletonList(IKafkaConstants.TOPIC_NAME)); 55 | return consumer; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/java/com/bfm/kafka/CustomObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import java.io.Serializable; 19 | 20 | public class CustomObject implements Serializable{ 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private String id; 25 | 26 | private String name; 27 | 28 | public String getId() { 29 | return id; 30 | } 31 | 32 | public void setId(String id) { 33 | this.id = id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public void setName(String name) { 41 | this.name = name; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/java/com/bfm/kafka/IKafkaConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | public interface IKafkaConstants { 19 | String KAFKA_BROKERS = "localhost:9092"; 20 | 21 | String CLIENT_ID="test-consumer"; 22 | 23 | String TOPIC_NAME="test"; 24 | 25 | String GROUP_ID_CONFIG="foo"; 26 | 27 | Integer MAX_NO_MESSAGE_FOUND_COUNT=100; 28 | 29 | String OFFSET_RESET_LATEST="latest"; 30 | 31 | String OFFSET_RESET_EARLIER="earliest"; 32 | 33 | Integer MAX_POLL_RECORDS=1; 34 | } 35 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /kafka-consumer-example/src/main/resources/oauth-configuration.properties: -------------------------------------------------------------------------------- 1 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 2 | oauth.server.token.endpoint.path=/token 3 | oauth.server.introspection.endpoint.path=/token/introspect 4 | oauth.server.client.id=test-consumer 5 | oauth.server.client.secret=7b3c23ef-8909-489e-bf4a-64a84abb197e 6 | oauth.server.grant.type=client_credentials 7 | oauth.server.scopes=test 8 | oauth.server.accept.unsecure.server=true -------------------------------------------------------------------------------- /kafka-oauth/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | 18 | 19 | # Project 20 | target/ 21 | /null/ 22 | /.checkstyle 23 | .eclipse-pmd 24 | .settings/org.springframework.ide.eclipse.prefs 25 | git.properties 26 | /logs/ 27 | /${sys:myserver.writable.root.dir}/ 28 | -------------------------------------------------------------------------------- /kafka-oauth/KeyCloak-Setup.md: -------------------------------------------------------------------------------- 1 | Copyright © 2020 BlackRock Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | # KeyCloak Setup 16 | 17 | - Download version 6.01 of KeyCloak Standalone Server from the following: 18 | - https://www.keycloak.org/downloads.html 19 | - Unzip to a preferred file location 20 | - Run the standalone file to start KeyCloak 21 | - If you wish to learn more about the KeyCloak configuration follow this link: 22 | - https://www.keycloak.org/docs/latest/server_installation/index.html#_standalone-mode 23 | 24 | ![image info](pictures/KeyCloak/standalone-boot-files.png) 25 | 26 | 27 | - Navigate to your running KeyCloak instance: 28 | - http://localhost:8080 29 | 30 | ## Create Admin User 31 | - The first time you navigate to the administration console you have to create administrator user. 32 | - Create an admin user, and login into the administration console. 33 | 34 | ## Create Client Scopes 35 | - Create the KeyCloak client scopes that represent topics and kafka brokers that need to be secured. 36 | - Client scopes are defined using the Uniform Resource Name (URN) format. 37 | 38 | urn:kafka:{resourceType}:{resourceName}:{operation} 39 | 40 | * resourceType: topic, group, cluster 41 | * resourceName: The name of the resource (topic name, group name, cluster name). 42 | * operation: read, write, create, delete, alter, describe, cluster_action 43 | 44 | - Please see Kafka documentation for valid combinations of resource types to operations. 45 | - [List of Kafka Operations and Resource Types](https://docs.confluent.io/current/kafka/authorization.html#acl-format) 46 | 47 | ##### For this example we will be using a topic called "test" and a consumer group called "foo". 48 | 49 | 50 | #### Kafka Broker Client Scopes 51 | - To allow the broker to be authorized the following scopes need to be created. 52 | 53 | urn:kafka:cluster:kafka-cluster:cluster_action 54 | 55 | #### Kafka Producer Client Scopes 56 | - Producers will need to be able to write and describe on a topic. 57 | 58 | urn:kafka:topic:test:describe 59 | urn:kafka:topic:test:write 60 | 61 | #### Kafka Consumer Client Scopes 62 | - Consumers will need to be able to read and describe both a group and a topic. 63 | 64 | urn:kafka:topic:test:describe 65 | urn:kafka:topic:test:read 66 | urn:kafka:group:foo:describe 67 | urn:kafka:group:foo:read 68 | 69 | ##### Create the Scopes in KeyCloak 70 | - To create a Client Scope navigate to the Client Scopes tab in the left menu. 71 | - Click create on the upper left hand corner of the table 72 | - Complete the form with the valid URN scope formatted name 73 | 74 | 75 | For Example: 76 | 77 | ![image info](pictures/KeyCloak/Add-ClientScopes.png) 78 | 79 | ## Create Kafka Broker Client 80 | - Create a "kafka-broker" client that represents the Kafka application that needs to perform cluster operations. 81 | - Make sure to enable the service account feature, this will allow the client to be authenticated and authorized in Kakfa. 82 | 83 | For Example: 84 | 85 | ![image info](pictures/KeyCloak/TestBroker.png) 86 | 87 | #### Grant Kafka Broker Client Scopes 88 | - Assign the broker scope specified above to the "kafka-broker" client 89 | 90 | For Example: 91 | 92 | ![image info](pictures/KeyCloak/TestBroker-ClientScopes.png) 93 | 94 | 95 | ## Create Test Producer Client 96 | - Create a "test-producer" client that represents the Kafka application that needs to perform read/describe operations. 97 | - Make sure to enable the service account feature, this will allow the client to be authenticated and authorized in Kakfa. 98 | 99 | For Example: 100 | 101 | ![image info](pictures/KeyCloak/TestProducer.png) 102 | 103 | #### Grant Test Producer Client Scopes 104 | - Assign the producer scopes specified above to the "test-producer" client 105 | 106 | For Example: 107 | 108 | ![image info](pictures/KeyCloak/TestProducer-ClientScopes.png) 109 | 110 | 111 | ## Create Test Consumer Client 112 | - Create a "test-consumer" client that represents the Consumer application that needs to perform write/describe operations. 113 | - Make sure to enable the service account feature, this will allow the client to be authenticated and authorized in Kakfa. 114 | 115 | For Example: 116 | 117 | ![image info](pictures/KeyCloak/TestConsumer.png) 118 | 119 | #### Grant Test Consumer Client Scopes 120 | - Assign the consumer scopes specified above to the "test-consumer" client 121 | 122 | For Example: 123 | 124 | ![image info](pictures/KeyCloak/TestConsumer-ClientScopes.png) 125 | 126 | ## Optional - Create Admin Client 127 | - This is an admin service account to use from the CLI. 128 | - The customized CLI will interact with the KeyCloak API to create a scope and add it to a specified client. 129 | - To be able to interact with the KeyCloak API an admin client is required. 130 | - Create a new client called "kafka-admin" 131 | 132 | For Example: 133 | 134 | ![image info](pictures/KeyCloak/AdminClient.png) 135 | 136 | #### Assign Service Account Roles 137 | - Add realm management roles 138 | 139 | ![image info](pictures/KeyCloak/Admin-ServiceAccountRoles.png) 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /kafka-oauth/README.md: -------------------------------------------------------------------------------- 1 | Copyright © 2020 BlackRock Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | # Kafka OAuth Library 16 | - Provides Authentication and Authorization via OAuth/OIDC 17 | 18 | ## Kafka Broker Configuration 19 | 20 | #### Download Apache Kafka 21 | - https://kafka.apache.org/downloads 22 | - Quick Tutorial on Kafka 23 | - https://kafka.apache.org/quickstart 24 | - Use this tutorial to get the Kafka running without any modifications 25 | - Once that is complete move on to adding the OAuth configuration to Kafka! 26 | 27 | #### Config Files 28 | - Create a properties file for your Broker OAuth client {broker-configuration.properties}. 29 | - The file should contain the following properties: 30 | - NOTES: 31 | - you will need to update the oauth.server.client.secret to be your client secret!!!!! 32 | - you can pass this properties under the kafka_server_jaas.conf 33 | 34 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 35 | oauth.server.token.endpoint.path=/token 36 | oauth.server.introspection.endpoint.path=/token/introspect 37 | oauth.server.client.id=kafka-broker 38 | oauth.server.client.secret=4ce54cfb-f359-4400-bd8e-810a1af10f71 39 | oauth.server.grant.type=client_credentials 40 | oauth.server.scopes=test 41 | oauth.server.accept.unsecure.server=true 42 | # oauth.server.accept.unsecure.server - this propertie is for SSL configuration, if you are using HTTP or a self-signed CERT set this true 43 | 44 | 45 | - Create a config file for your JAAS security {kafka_server_jaas.conf} 46 | - The file must contain the following: 47 | - NOTE: the properties started by oauth.server only needed if you don't want to user the broker-configuration.properties file! 48 | 49 | KafkaServer { 50 | org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required 51 | oauth.server.base.uri="http://localhost:8080/auth/realms/master/protocol/openid-connect" 52 | oauth.server.token.endpoint.path="/token" 53 | oauth.server.introspection.endpoint.path="/token/introspect" 54 | oauth.server.client.id="kafka-broker" 55 | oauth.server.client.secret="4ce54cfb-f359-4400-bd8e-810a1af10f71" 56 | oauth.server.grant.type="client_credentials" 57 | oauth.server.scopes="test" 58 | oauth.server.accept.unsecure.server="true"; 59 | }; 60 | 61 | 62 | - Update the Kafka server.properties file. 63 | - The original can be found in the kafka config folder, add these properties to the end of the file: 64 | 65 | 66 | # The following were added to test OAuth Bearer SASL 67 | listeners=SASL_PLAINTEXT://localhost:9092 68 | advertised.listeners=SASL_PLAINTEXT://localhost:9092 69 | listener.name.sasl_plaintext.oauthbearer.sasl.login.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler 70 | listener.name.sasl_plaintext.oauthbearer.sasl.server.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateValidatorCallbackHandler 71 | 72 | ############################# Security/SASL Settings ############################# 73 | security.inter.broker.protocol=SASL_PLAINTEXT 74 | sasl.mechanism.inter.broker.protocol=OAUTHBEARER 75 | sasl.enabled.mechanisms=OAUTHBEARER 76 | connections.max.reauth.ms=60000 77 | 78 | 79 | 80 | ############## Authorizer ############### 81 | authorizer.class.name=com.bfm.kafka.security.oauthbearer.CustomAuthorizer 82 | principal.builder.class=com.bfm.kafka.security.oauthbearer.CustomPrincipalBuilder 83 | 84 | #### Add dependencies to Kafka folder 85 | - Build the kafka-oauth JAR and then copy it from the target directory into the Kafka lib folder. 86 | - This is needed for Kafka to utilize the custom classes communicating with the OAuth server. 87 | 88 | C:> git clone https://blade-git.blackrock.com/cachematrix/lib-kafka-oauth.git 89 | C:> cd lib-kafka-oauth\ 90 | C:\lib-kafka-oauth> mvn install - NOTE: this will build all JARs (Kafka OAuth, Test consumer, and Test Producer) 91 | . 92 | . 93 | . 94 | C:\lib-kafka-oauth> copy kafka-oauth\target\libkafka.oauthbearer-1.0.0.jar {KAFKA_DIRECTORY}\libs\. 95 | 96 | - Move your updated server.properties file into the kafka config directory. 97 | - Move your kafka_server_jaas.conf file to the kafka config directory. 98 | 99 | ## Run Kafka with OAuth 100 | - Open two CMD windows to run the zookeeper and broker 101 | - Run the Zookeeper 102 | 103 | --- 104 | 105 | bin\windows\zookeeper-server-start.bat config\zookeeper.properties 106 | 107 | - Run the Broker 108 | 109 | --- 110 | set KAFKA_OPTS=-Djava.security.auth.login.config={PATH_TO_JASS_CONFIG_FILE}\kafka_server_jaas.conf 111 | 112 | set KAFKA_OAUTH_SERVER_PROP_FILE={PATH_TO_PROPERTIES_FILE}\broker-configuration.properties 113 | 114 | bin\windows\kafka-server-start.bat config\server.properties 115 | 116 | 117 | -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/Add-ClientScopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/Add-ClientScopes.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/Admin-ServiceAccountRoles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/Admin-ServiceAccountRoles.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/AdminClient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/AdminClient.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestBroker-ClientScopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestBroker-ClientScopes.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestBroker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestBroker.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestConsumer-ClientScopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestConsumer-ClientScopes.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestConsumer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestConsumer.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestProducer-ClientScopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestProducer-ClientScopes.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/TestProducer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/TestProducer.png -------------------------------------------------------------------------------- /kafka-oauth/pictures/KeyCloak/standalone-boot-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kafka-security/oauth/efe16f7844ea7661b1aeb4260e22f6a501b17d58/kafka-oauth/pictures/KeyCloak/standalone-boot-files.png -------------------------------------------------------------------------------- /kafka-oauth/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | brs 8 | libkafka.oauthbearer 9 | 1.0.0 10 | jar 11 | 12 | 13 | 14 | junit 15 | junit 16 | 4.12 17 | 18 | 19 | org.apache.kafka 20 | kafka_2.12 21 | 2.2.0 22 | 23 | 24 | javax.validation 25 | validation-api 26 | 2.0.1.Final 27 | 28 | 29 | org.apache.logging.log4j 30 | log4j-api 31 | 2.7 32 | 33 | 34 | org.apache.logging.log4j 35 | log4j-core 36 | 2.7 37 | 38 | 39 | org.apache.logging.log4j 40 | log4j-slf4j-impl 41 | 2.7 42 | 43 | 44 | 45 | org.mockito 46 | mockito-core 47 | 2.8.9 48 | test 49 | 50 | 51 | org.powermock 52 | powermock-reflect 53 | 1.6.5 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-jar-plugin 63 | 64 | 65 | src/main/resources/META-INF/MANIFEST.MF 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-compiler-plugin 72 | 3.8.0 73 | 74 | 1.8 75 | 1.8 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-deploy-plugin 81 | 2.7 82 | 83 | true 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/CustomAuthorizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import kafka.network.RequestChannel.Session; 19 | import kafka.security.auth.Acl; 20 | import kafka.security.auth.Authorizer; 21 | import kafka.security.auth.Operation; 22 | import kafka.security.auth.Resource; 23 | import org.apache.kafka.common.security.auth.KafkaPrincipal; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import scala.collection.immutable.Map; 27 | import scala.collection.immutable.Set; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * The type Oauth authorizer. 34 | */ 35 | public class CustomAuthorizer implements Authorizer { 36 | private static final Logger log = LoggerFactory.getLogger(CustomAuthorizer.class); 37 | 38 | /** 39 | * Instantiates a new Custom authorizer. 40 | */ 41 | public CustomAuthorizer() { 42 | } 43 | 44 | /** 45 | * Check scopes from JWT to validate topic/resource operations. 46 | * 47 | * @param session session info 48 | * @param operation Kafka operation 49 | * @param resource resource being operated one 50 | * @return true/false 51 | */ 52 | @Override 53 | public boolean authorize(Session session, Operation operation, Resource resource) { 54 | try { 55 | log.info("Starting Authorization."); 56 | // log.info("Session Info: {}", session.toString()); 57 | log.info("Operation request Info: {}", operation.toString()); 58 | log.info("Resource request Info: {}", resource.toString()); 59 | if (!(session.principal() instanceof CustomPrincipal)) { 60 | log.error("Session Principal is not using the proper class. Should be instance of CustomPrincipal."); 61 | return false; 62 | } 63 | 64 | CustomPrincipal principal = (CustomPrincipal) session.principal(); 65 | if (principal.getOauthBearerTokenJwt() == null) { 66 | log.error("Custom Principal does not contain token information."); 67 | return false; 68 | } 69 | 70 | OAuthBearerTokenJwt jwt = principal.getOauthBearerTokenJwt(); 71 | if (jwt.scope() == null || jwt.scope().isEmpty()) { 72 | log.error("No scopes provided in JWT. Unable to Authorize."); 73 | return false; 74 | } 75 | 76 | java.util.Set scopes = jwt.scope(); 77 | List scopeInfo = parseScopes(scopes); 78 | String operationStr = operation.toJava().toString(); 79 | return checkAuthorization(scopeInfo, resource, operationStr); 80 | } catch (Exception e) { 81 | log.error("Error in authorization. ", e); 82 | } 83 | return false; 84 | } 85 | 86 | /** 87 | * Check authorization against scopes. 88 | * 89 | * @param scopeInfo list of scopes 90 | * @param resource resource info 91 | * @param operation operation performed 92 | * @return true /false 93 | */ 94 | protected boolean checkAuthorization(List scopeInfo, Resource resource, String operation) { 95 | for (int i = 0; i < scopeInfo.size(); i++) { 96 | OAuthScope scope = scopeInfo.get(i); 97 | String lowerCaseOperation = operation.toLowerCase(); 98 | String lowerCaseResourceName = resource.name().toLowerCase(); 99 | String lowerCaseCaseResourceType = resource.resourceType().toString().toLowerCase(); 100 | 101 | boolean operationVal = scope.getOperation().toLowerCase().equals(lowerCaseOperation); 102 | boolean nameVal = scope.getResourceName().toLowerCase().equals(lowerCaseResourceName); 103 | boolean typeVal = scope.getResourceType().toLowerCase().equals(lowerCaseCaseResourceType); 104 | 105 | if (operationVal && nameVal && typeVal) { 106 | log.info("Successfully Authorized."); 107 | return true; 108 | } 109 | } 110 | log.info("Not Authorized to operate on the given resource."); 111 | return false; 112 | } 113 | 114 | /** 115 | * Parse topic and Operation out of scope. 116 | * 117 | * @param scopes set of scopes 118 | * @return return list of pairs, each pair is a topic/operation

Scope format urn:kafka::: 119 | */ 120 | protected List parseScopes(java.util.Set scopes) { 121 | List result = new ArrayList<>(); 122 | for (String scope : scopes) { 123 | String[] scopeArray = scope.split("\\s+"); 124 | for (String str : scopeArray){ 125 | convertScope(result, str); 126 | } 127 | } 128 | return result; 129 | } 130 | 131 | /** 132 | * convertScope. 133 | * @param result list of scopesInfo 134 | * @param scope string of scope 135 | */ 136 | private void convertScope(List result, String scope) { 137 | String[] str = scope.split(":"); 138 | if (str.length == 5) { 139 | String type = str[2]; 140 | String name = str[3]; 141 | String operation = str[4]; 142 | OAuthScope oAuthScope = new OAuthScope(); 143 | oAuthScope.setOperation(operation); 144 | oAuthScope.setResourceName(name); 145 | oAuthScope.setResourceType(type); 146 | result.add(oAuthScope); 147 | } else { 148 | log.error("Unable to parse scope. Incorrect format: {}.", scope); 149 | } 150 | } 151 | 152 | @Override 153 | public void addAcls(Set acls, Resource resource) { 154 | 155 | } 156 | 157 | @Override 158 | public boolean removeAcls(Set acls, Resource resource) { 159 | return false; 160 | } 161 | 162 | @Override 163 | public boolean removeAcls(Resource resource) { 164 | return false; 165 | } 166 | 167 | @Override 168 | public Set getAcls(Resource resource) { 169 | return null; 170 | } 171 | 172 | @Override 173 | public Map> getAcls(KafkaPrincipal principal) { 174 | return null; 175 | } 176 | 177 | @Override 178 | public Map> getAcls() { 179 | return null; 180 | } 181 | 182 | @Override 183 | public void close() { 184 | 185 | } 186 | 187 | 188 | @Override 189 | public void configure(java.util.Map map) { 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/CustomPrincipal.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.auth.KafkaPrincipal; 19 | 20 | /** 21 | * The type Custom principal. 22 | */ 23 | public class CustomPrincipal extends KafkaPrincipal { 24 | 25 | private OAuthBearerTokenJwt oauthBearerTokenJwt; 26 | 27 | /** 28 | * Instantiates a new Custom principal. 29 | * 30 | * @param principalType the principal type 31 | * @param name the name 32 | */ 33 | public CustomPrincipal(String principalType, String name) { 34 | super(principalType, name); 35 | } 36 | 37 | /** 38 | * Gets oauth bearer token jwt. 39 | * 40 | * @return the oauth bearer token jwt 41 | */ 42 | public OAuthBearerTokenJwt getOauthBearerTokenJwt() { 43 | return oauthBearerTokenJwt; 44 | } 45 | 46 | /** 47 | * Sets oauth bearer token jwt. 48 | * 49 | * @param oauthBearerTokenJwt the oauth bearer token jwt 50 | */ 51 | public void setOauthBearerTokenJwt(OAuthBearerTokenJwt oauthBearerTokenJwt) { 52 | this.oauthBearerTokenJwt = oauthBearerTokenJwt; 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "CustomPrincipal{" + 58 | "oauthBearerTokenJwt=" + oauthBearerTokenJwt + 59 | "} " + super.toString(); 60 | } 61 | } -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/CustomPrincipalBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.KafkaException; 19 | import org.apache.kafka.common.security.auth.AuthenticationContext; 20 | import org.apache.kafka.common.security.auth.KafkaPrincipalBuilder; 21 | import org.apache.kafka.common.security.auth.SaslAuthenticationContext; 22 | 23 | /** 24 | * The type Custom principal builder. 25 | */ 26 | public class CustomPrincipalBuilder implements KafkaPrincipalBuilder { 27 | @Override 28 | public CustomPrincipal build(AuthenticationContext authenticationContext) throws KafkaException{ 29 | try { 30 | CustomPrincipal customPrincipal; 31 | 32 | if (authenticationContext instanceof SaslAuthenticationContext) { 33 | SaslAuthenticationContext context = (SaslAuthenticationContext) authenticationContext; 34 | OAuthBearerTokenJwt token = (OAuthBearerTokenJwt) context.server() 35 | .getNegotiatedProperty("OAUTHBEARER.token"); 36 | 37 | customPrincipal = new CustomPrincipal("User", token.principalName()); 38 | customPrincipal.setOauthBearerTokenJwt(token); 39 | 40 | return customPrincipal; 41 | } else { 42 | throw new KafkaException("Failed to build CustomPrincipal. SaslAuthenticationContext is required."); 43 | } 44 | } catch (Exception ex) { 45 | throw new KafkaException("Failed to build CustomPrincipal due to: ", ex); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/EnvironmentVariablesUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | /** 19 | * The type Environment variables util. 20 | */ 21 | public class EnvironmentVariablesUtil { 22 | 23 | /** 24 | * Gets boolean environment variable. 25 | * 26 | * @param envName the env name 27 | * @param defaultValue the default value 28 | * @return the boolean environment variable 29 | */ 30 | public static Boolean getBooleanEnvironmentVariable(String envName, Boolean defaultValue) { 31 | Boolean result; 32 | String env = System.getenv(envName); 33 | if (env == null) { 34 | result = defaultValue; 35 | } else { 36 | result = Boolean.valueOf(env); 37 | } 38 | return result; 39 | } 40 | 41 | /** 42 | * Gets string environment variable. 43 | * 44 | * @param envName the env name 45 | * @param defaultValue the default value 46 | * @return the string environment variable 47 | */ 48 | public static String getStringEnvironmentVariable(String envName, String defaultValue) { 49 | String result; 50 | String env = System.getenv(envName); 51 | if (env == null) { 52 | result = defaultValue; 53 | } else { 54 | result = env; 55 | } 56 | return result; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.auth.AuthenticateCallbackHandler; 19 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import javax.security.auth.callback.Callback; 24 | import javax.security.auth.callback.UnsupportedCallbackException; 25 | import javax.security.auth.login.AppConfigurationEntry; 26 | import java.io.IOException; 27 | import java.util.Collections; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Objects; 31 | 32 | /** 33 | * As the implementation of the Kafka AuthenticateCallbackHandler interface, this class is an abstract callback handler 34 | * that allows subclasses to reuse common logic and to inject custom functions 35 | * 36 | * @param the generic type for callback functions 37 | */ 38 | public abstract class OAuthAuthenticateCallbackHandler implements AuthenticateCallbackHandler { 39 | //region Member Variables 40 | 41 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateCallbackHandler.class); 42 | private OAuthService oauthService; 43 | private boolean configured = false; 44 | private Map moduleOptions = null; 45 | private List jaasConfigEntries; 46 | private Class callBackHandlerClass; 47 | 48 | //endregion 49 | 50 | //region Constructors 51 | 52 | /** 53 | * Instantiates a new O auth authenticate callback handler. 54 | * 55 | * @param oauthService the oauth service 56 | * @param callBackHandlerClass the call back handler class 57 | */ 58 | public OAuthAuthenticateCallbackHandler(OAuthService oauthService, Class callBackHandlerClass) { 59 | // validate the parameters 60 | Objects.requireNonNull(oauthService); 61 | Objects.requireNonNull(callBackHandlerClass); 62 | 63 | // initialize member variables 64 | this.oauthService = oauthService; 65 | this.callBackHandlerClass = callBackHandlerClass; 66 | } 67 | 68 | //endregion 69 | 70 | //region Protected Properties 71 | 72 | /** 73 | * Gets oauth service. 74 | * 75 | * @return the oauth service 76 | */ 77 | protected OAuthService getOauthService() { 78 | return this.oauthService; 79 | } 80 | 81 | /** 82 | * Is configured boolean. 83 | * 84 | * @return the boolean 85 | */ 86 | protected boolean isConfigured() { 87 | return this.configured; 88 | } 89 | 90 | //endregion 91 | 92 | //region Public Methods 93 | 94 | /** 95 | * Configures this callback handler for the specified SASL mechanism. 96 | * 97 | * @param configs Key-value pairs containing the parsed configuration options of the client or broker. 98 | * Note that these are the Kafka configuration options and not the JAAS configuration 99 | * options. JAAS config options may be obtained from `jaasConfigEntries` for callbacks 100 | * which obtain some configs from the JAAS configuration. For configs that may be specified 101 | * as both Kafka config as well as JAAS config (e.g. sasl.kerberos.service.name), the 102 | * configuration is treated as invalid if conflicting values are provided. 103 | * @param saslMechanism Negotiated SASL mechanism. For clients, this is the SASL mechanism configured for the 104 | * client. For brokers, this is the mechanism negotiated with the client and is one of the 105 | * mechanisms enabled on the broker. 106 | * @param jaasConfigEntries JAAS configuration entries from the JAAS login context. This list contains a single 107 | * entry for clients and may contain more than one entry for brokers if multiple mechanisms 108 | * are enabled on a listener using static JAAS configuration where there is no mapping 109 | * between mechanisms and login module entries. In this case, callback handlers can use 110 | * the login module in `jaasConfigEntries` to identify the entry corresponding to 111 | * `saslMechanism`. Alternatively, dynamic JAAS configuration option 112 | * SaslConfigs.SASL_JAAS_CONFIG may be configured on brokers with listener and mechanism 113 | * prefix, in which case only the configuration entry corresponding to `saslMechanism` will 114 | * be provided in `jaasConfigEntries`. 115 | */ 116 | @Override 117 | public void configure(Map configs, String saslMechanism, List jaasConfigEntries) { 118 | log.debug("Starting to configure OAuth authentication callback handler."); 119 | 120 | log.debug("Validate method parameters."); 121 | Objects.requireNonNull(configs); 122 | Objects.requireNonNull(saslMechanism); 123 | Objects.requireNonNull(jaasConfigEntries); 124 | String errMsg; 125 | 126 | // validate the SASL Mechanism is set correctly 127 | if (OAuthBearerLoginModule.OAUTHBEARER_MECHANISM.equals(saslMechanism)) { 128 | if (jaasConfigEntries.size() == 1 && jaasConfigEntries.get(0) != null) { 129 | this.moduleOptions = Collections.unmodifiableMap( 130 | (Map) jaasConfigEntries.get(0).getOptions()); 131 | //Configure OAuthService using the JAAS file properties 132 | this.oauthService.setOAuthConfiguration(moduleOptions); 133 | // the handle has been configured 134 | configured = true; 135 | } else { 136 | errMsg = String.format( 137 | "Must supply exactly 1 non-null JAAS mechanism configuration (size was %d)", 138 | jaasConfigEntries.size()); 139 | 140 | log.error(errMsg); 141 | } 142 | } else { 143 | errMsg = String.format("Unexpected SASL mechanism: %s", saslMechanism); 144 | 145 | log.error(errMsg); 146 | } 147 | } 148 | 149 | /** 150 | * Handle OAuth bearer token callbacks which invokes the generic function provided by the sub classs 151 | *

152 | * The handle method implementation checks the instance(s) of the Callback object(s) passed in to retrieve or 153 | * display the requested information. 154 | * 155 | * @param callbacks An array of Callback objects provided by an underlying security service which contains the 156 | * information requested to be retrieved or displayed. 157 | * @throws IOException If the generic callback function throws an Exception 158 | * @throws UnsupportedCallbackException If the input callbackType does not match the subclass 159 | */ 160 | @Override 161 | public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { 162 | log.debug("Starting to handle OAuth bearer token login callbacks."); 163 | 164 | // check to see if the handler has been configured 165 | log.debug("Check to see of the handler has been configured."); 166 | if (!isConfigured()) { 167 | String errMsg = "Callback handler not configured"; 168 | 169 | log.error(errMsg); 170 | 171 | throw new IllegalStateException(errMsg); 172 | } 173 | 174 | // loop through all of the callbacks looking for the OAuth bearer token callbacks 175 | log.debug("Loop through all of the callbacks looking for the OAuth bearer token callbacks."); 176 | for (Callback callback : callbacks) { 177 | // check the type of the callback 178 | log.debug("Check the type of the callback."); 179 | Class callBackType = callback.getClass(); 180 | if (callBackType.equals(this.callBackHandlerClass)) { 181 | // the callback is the correct type for this handler, process the callback 182 | log.debug("The callback is the correct type of this handler, process the callback."); 183 | try { 184 | T oauthBearerTokenCallback = (T) callback; 185 | handleCallback(oauthBearerTokenCallback); 186 | } catch (Exception e) { // handle exception here so that we can log exception where it happens 187 | String errMsg = String.format( 188 | "Error handing OAuth bearer token login callback, Message: %s", 189 | e.getMessage()); 190 | 191 | log.error(errMsg); 192 | 193 | throw new IOException(errMsg, e); 194 | } 195 | } else { 196 | // the callback is not the correct type for this handler, throw a exception 197 | String errMsg = String.format( 198 | "Callback is not a OAuthBearerTokenCallback, Type: %s", 199 | callback); 200 | log.debug(errMsg); 201 | 202 | throw new UnsupportedCallbackException(callback); 203 | } 204 | } 205 | log.debug("Finished handling OAuth bearer token login callbacks."); 206 | } 207 | 208 | /** 209 | * Implementation of the interface. 210 | * This class does not have resource dependent attributes, therefore this method is empty. 211 | */ 212 | @Override 213 | public void close() { 214 | } 215 | 216 | //endregion 217 | 218 | //region Protected Methods 219 | 220 | /** 221 | * Handle callback and should be implemented by sub classes 222 | * 223 | * @param oauthBearerTokenCallback the oauth bearer token callback 224 | */ 225 | protected abstract void handleCallback(T oauthBearerTokenCallback) throws IOException; 226 | 227 | //endregion 228 | } 229 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateLoginCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.io.IOException; 23 | import java.util.Objects; 24 | 25 | /** 26 | * A Callback for use by the SaslClient and Login implementations when they require an OAuth 2 bearer token. 27 | */ 28 | public class OAuthAuthenticateLoginCallbackHandler extends OAuthAuthenticateCallbackHandler { 29 | //region Member Variables 30 | 31 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateLoginCallbackHandler.class); 32 | 33 | //endregion 34 | 35 | //region Constructors 36 | 37 | /** 38 | * Instantiates a new O auth authenticate login callback handler. 39 | */ 40 | public OAuthAuthenticateLoginCallbackHandler() { 41 | super(new OAuthServiceImpl(), OAuthBearerTokenCallback.class); 42 | } 43 | 44 | //endregion 45 | 46 | //region Protected Methods 47 | 48 | /** 49 | * This method handles attempt to login by requesting an access token from the OAuth Server 50 | * @param oauthBearerTokenCallback the oauth bearer token callback 51 | * @throws IOException - if OAuth Server did not grant an access token 52 | */ 53 | protected void handleCallback(OAuthBearerTokenCallback oauthBearerTokenCallback) throws IOException { 54 | log.debug("Starting to handle OAuth bearer token login callback."); 55 | 56 | // make sure that the parameters are valid 57 | log.debug("Validate method parameters."); 58 | Objects.requireNonNull(oauthBearerTokenCallback); 59 | 60 | // check to see if oauthBearerTokenCallback already had a token 61 | if (oauthBearerTokenCallback.token() != null) { 62 | 63 | String errMsg = "OAuth bearer token callback already had a token."; 64 | 65 | log.error(errMsg); 66 | 67 | throw new IllegalArgumentException(errMsg); 68 | } 69 | 70 | // acquire access token 71 | log.debug("Acquire access token for OAuth bearer token callback."); 72 | OAuthBearerTokenJwt token = this.getOauthService().requestAccessToken(); 73 | 74 | // check to see an access token was returned 75 | log.debug("Check to see if an access token as returned."); 76 | if (token == null) { 77 | String errMsg = "Access token was not returned."; 78 | 79 | log.error(errMsg); 80 | 81 | throw new IllegalArgumentException(errMsg); 82 | } 83 | 84 | // access token was returned, set the access token on the OAuth bearer token callback 85 | log.info("Access token was returned, set the access token on the OAuth bearer token callback."); 86 | oauthBearerTokenCallback.token(token); 87 | log.debug("Finished handling OAuth bearer token login callback."); 88 | } 89 | 90 | //endregion 91 | } 92 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateValidatorCallbackHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback; 19 | import org.apache.kafka.common.utils.Time; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.io.IOException; 24 | import java.util.Objects; 25 | 26 | /** 27 | * A Callback for use by the SaslClient and Validator implementations when they require an OAuth 2 bearer token. 28 | */ 29 | public class OAuthAuthenticateValidatorCallbackHandler extends OAuthAuthenticateCallbackHandler { 30 | //region Member Variables 31 | 32 | private final Logger log = LoggerFactory.getLogger(OAuthAuthenticateValidatorCallbackHandler.class); 33 | private Time time = Time.SYSTEM; 34 | 35 | 36 | //endregion 37 | 38 | //region Constructors 39 | 40 | /** 41 | * Instantiates a new O auth authenticate validator callback handler. 42 | */ 43 | public OAuthAuthenticateValidatorCallbackHandler() { 44 | super(new OAuthServiceImpl(), OAuthBearerValidatorCallback.class); 45 | } 46 | 47 | //endregion 48 | 49 | //region Protected Methods 50 | 51 | /** 52 | * This callback validates an access token in the OAuth Server 53 | * @param callback 54 | * @throws IOException - if the access token cannot be validated in the OAuth Server 55 | */ 56 | protected void handleCallback(OAuthBearerValidatorCallback callback) throws IOException { 57 | log.debug("Starting to handle OAuth bearer token validation callback."); 58 | 59 | // make sure that the parameters are valid 60 | log.debug("Validate method parameters."); 61 | Objects.requireNonNull(callback); 62 | 63 | String accessToken = callback.tokenValue(); 64 | if (accessToken == null) { 65 | String errMsg = "Callback missing required token value."; 66 | log.error(errMsg); 67 | throw new IllegalArgumentException(errMsg); 68 | } 69 | 70 | log.debug("Validate access token."); 71 | OAuthBearerTokenJwt token = this.getOauthService().validateAccessToken(accessToken); 72 | 73 | // the access token is valid, set token on the callback 74 | if (token == null) { 75 | log.info("The access token is not valid or has expired."); 76 | } else { 77 | log.info("The access token is valid, set token on the callback."); 78 | } 79 | 80 | callback.token(token); 81 | 82 | log.debug("Finished handling OAuth bearer token validation callback."); 83 | } 84 | 85 | //endregion 86 | } 87 | 88 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthBearerTokenJwt.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerToken; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Set; 23 | import java.util.TreeSet; 24 | 25 | /** 26 | * The b64token value as defined in RFC 6750 Section 2.1 along with the token's specific scope and lifetime and 27 | * principal name. 28 | */ 29 | public class OAuthBearerTokenJwt implements OAuthBearerToken { 30 | 31 | //region Constants 32 | 33 | private static final String SUB = "sub"; 34 | private static final String SCOPE = "scope"; 35 | private static final String EXP = "exp"; 36 | private static final String IAT = "iat"; 37 | private static final String JTI = "jti"; 38 | 39 | //endregion 40 | 41 | //region Member Variables 42 | 43 | private String accessToken; 44 | private long lifetimeMs; 45 | private String principalName; 46 | private Long startTimeMs; 47 | private Set scope; 48 | private long expirationTime; 49 | private String jti; 50 | 51 | //endregion 52 | 53 | //region Constructors 54 | 55 | /** 56 | * Initializes a new instance of the OAuthBearerTokenJwt class. 57 | * 58 | * @param accessToken The b64token value as defined in RFC 6750 Section 2.1 59 | * @param lifeTimeMs The token's lifetime, expressed as the number of milliseconds since the epoch, as per RFC 6749 Section 1.4 60 | * @param startTimeMs When the credential became valid, in terms of the number of milliseconds since the epoch, if known, otherwise null. 61 | * @param principalName The name of the principal to which this credential applies 62 | */ 63 | public OAuthBearerTokenJwt(String accessToken, long lifeTimeMs, long startTimeMs, String principalName) { 64 | super(); 65 | 66 | this.accessToken = accessToken; 67 | this.principalName = principalName; 68 | this.lifetimeMs = startTimeMs + (lifeTimeMs * 1000); 69 | this.startTimeMs = startTimeMs; 70 | this.expirationTime = startTimeMs + (lifeTimeMs * 1000); 71 | } 72 | 73 | /** 74 | * Initializes a new instance of the OAuthBearerTokenJwt class based on a collection of key value pairs 75 | * 76 | * @param jwtToken - A JWT Token expressed as a key value pair 77 | * @param accessToken The b64token value as defined in RFC 6750 Section 2.1 78 | */ 79 | public OAuthBearerTokenJwt(Map jwtToken, String accessToken) { 80 | super(); 81 | this.accessToken = accessToken; 82 | this.principalName = (String) jwtToken.get(SUB); 83 | 84 | if (this.scope == null) { 85 | this.scope = new TreeSet<>(); 86 | } 87 | 88 | if (jwtToken.get(SCOPE) instanceof String) { 89 | this.scope.add((String) jwtToken.get(SCOPE)); 90 | } else if (jwtToken.get(SCOPE) instanceof List) { 91 | for (String s : (List) jwtToken.get(SCOPE)) { 92 | this.scope.add(s); 93 | } 94 | } 95 | 96 | Object exp = jwtToken.get(EXP); 97 | if (exp instanceof Integer) { 98 | this.expirationTime = Integer.toUnsignedLong((Integer) jwtToken.get(EXP)); 99 | } else { 100 | this.expirationTime = (Long) jwtToken.get(EXP); 101 | } 102 | 103 | Object iat = jwtToken.get(IAT); 104 | if (iat instanceof Integer) { 105 | this.startTimeMs = Integer.toUnsignedLong((Integer) jwtToken.get(IAT)); 106 | } else { 107 | this.startTimeMs = (Long) jwtToken.get(IAT); 108 | } 109 | 110 | this.lifetimeMs = expirationTime * 1000; 111 | this.jti = (String) jwtToken.get(JTI); 112 | } 113 | 114 | //endregion 115 | 116 | //region Public Properties 117 | 118 | @Override 119 | public String value() { 120 | return this.accessToken; 121 | } 122 | 123 | @Override 124 | public Set scope() { 125 | return this.scope; 126 | } 127 | 128 | @Override 129 | public long lifetimeMs() { 130 | return this.lifetimeMs; 131 | } 132 | 133 | @Override 134 | public String principalName() { 135 | return this.principalName; 136 | } 137 | 138 | @Override 139 | public Long startTimeMs() { 140 | return this.startTimeMs != null ? this.startTimeMs : 0; 141 | } 142 | 143 | public long expirationTime() { 144 | return this.expirationTime; 145 | } 146 | 147 | //endregion 148 | 149 | //region Public Methods 150 | 151 | @Override 152 | public String toString() { 153 | return "OauthBearerTokenJwt {" + 154 | "value='" + accessToken + '\'' + 155 | ", lifetimeMs=" + lifetimeMs + 156 | ", principalName='" + principalName + '\'' + 157 | ", startTimeMs=" + startTimeMs + 158 | ", scope=" + scope + 159 | ", expirationTime=" + expirationTime + 160 | ", jti='" + jti + '\'' + 161 | '}'; 162 | } 163 | 164 | //endregion 165 | } -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.io.FileInputStream; 22 | import java.io.FileNotFoundException; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.util.Map; 26 | import java.util.Objects; 27 | import java.util.Properties; 28 | 29 | /** 30 | * The type O auth configuration. 31 | */ 32 | public class OAuthConfiguration { 33 | 34 | //region Constants 35 | 36 | private static final String KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR = "KAFKA_OAUTH_SERVER_PROP_FILE"; 37 | private static final String KAFKA_OAUTH_SERVER_BASE_URI_ENV_VAR = "KAFKA_OAUTH_SERVER_BASE_URI"; 38 | private static final String KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH_ENV_VAR = "KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH"; 39 | private static final String KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH_ENV_VAR = "KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH"; 40 | private static final String KAFKA_OAUTH_SERVER_CLIENT_ID_ENV_VAR = "KAFKA_OAUTH_SERVER_CLIENT_ID"; 41 | private static final String KAFKA_OAUTH_SERVER_CLIENT_SECRET_ENV_VAR = "KAFKA_OAUTH_SERVER_CLIENT_SECRET"; 42 | private static final String KAFKA_OAUTH_SERVER_GRANT_TYPE_ENV_VAR = "KAFKA_OAUTH_SERVER_GRANT_TYPE"; 43 | private static final String KAFKA_OAUTH_SERVER_SCOPES_ENV_VAR = "KAFKA_OAUTH_SERVER_SCOPES"; 44 | private static final String KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER_ENV_VAR = "KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER"; 45 | 46 | private static final String KAFKA_OAUTH_SERVER_BASE_URI = "oauth.server.base.uri"; 47 | private static final String KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH = "oauth.server.token.endpoint.path"; 48 | private static final String KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH = "oauth.server.introspection.endpoint.path"; 49 | private static final String KAFKA_OAUTH_SERVER_CLIENT_ID = "oauth.server.client.id"; 50 | private static final String KAFKA_OAUTH_SERVER_CLIENT_SECRET = "oauth.server.client.secret"; 51 | private static final String KAFKA_OAUTH_SERVER_GRANT_TYPE = "oauth.server.grant.type"; 52 | private static final String KAFKA_OAUTH_SERVER_SCOPES = "oauth.server.scopes"; 53 | private static final String KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER = "oauth.server.accept.unsecure.server"; 54 | 55 | //endregion 56 | 57 | //region Member Variables 58 | private final Logger log = LoggerFactory.getLogger(OAuthConfiguration.class); 59 | private String baseServerUri; 60 | private String tokenEndpointPath; 61 | private String introspectionEndpointPath; 62 | private String clientId; 63 | private String clientSecret; 64 | private String grantType; 65 | private String scopes; 66 | private Boolean unsecureServer; 67 | 68 | //endregion 69 | 70 | //region Constructors 71 | 72 | /** 73 | * Instantiates a new O auth configuration. 74 | */ 75 | public OAuthConfiguration() { 76 | 77 | try { 78 | log.debug("Starting to initialize OAuth configuration"); 79 | 80 | log.debug("Look for OAuthConfiguration property file."); 81 | Properties prop = this.getConfigurationFileProperties(); 82 | 83 | 84 | // get the OAuth server base Uri 85 | log.debug("Configure the OAuth server base Uri"); 86 | String defaultBaseServerUri = null; 87 | if (prop.containsKey(KAFKA_OAUTH_SERVER_BASE_URI)) { 88 | defaultBaseServerUri = prop.getProperty(KAFKA_OAUTH_SERVER_BASE_URI); 89 | } 90 | 91 | this.baseServerUri = EnvironmentVariablesUtil.getStringEnvironmentVariable( 92 | KAFKA_OAUTH_SERVER_BASE_URI_ENV_VAR, 93 | defaultBaseServerUri); 94 | 95 | // get the OAuth server token path 96 | log.debug("Configure the OAuth server token endpoint path."); 97 | String defaultTokenEndpointPath = null; 98 | if (prop.containsKey(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH)) { 99 | defaultTokenEndpointPath = prop.getProperty(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH); 100 | } 101 | 102 | this.tokenEndpointPath = EnvironmentVariablesUtil.getStringEnvironmentVariable( 103 | KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH_ENV_VAR, 104 | defaultTokenEndpointPath); 105 | 106 | // get the OAuth server introspection endpoint path 107 | log.debug("Configure the OAuth server introspection endpoint path."); 108 | String defaultIntrospectionEndpointPath = null; 109 | if (prop.containsKey(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH)) { 110 | defaultIntrospectionEndpointPath = prop.getProperty(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH); 111 | } 112 | 113 | this.introspectionEndpointPath = EnvironmentVariablesUtil.getStringEnvironmentVariable( 114 | KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH_ENV_VAR, 115 | defaultIntrospectionEndpointPath); 116 | 117 | // get the OAuth server client id 118 | log.debug("Configure the OAuth server client id."); 119 | String defaultClientId = null; 120 | if (prop.containsKey(KAFKA_OAUTH_SERVER_CLIENT_ID)) { 121 | defaultClientId = prop.getProperty(KAFKA_OAUTH_SERVER_CLIENT_ID); 122 | } 123 | 124 | this.clientId = EnvironmentVariablesUtil.getStringEnvironmentVariable( 125 | KAFKA_OAUTH_SERVER_CLIENT_ID_ENV_VAR, 126 | defaultClientId); 127 | 128 | // get the OAuth server client secret 129 | log.debug("Configure the OAuth server client secret."); 130 | String defaultClientSecret = null; 131 | if (prop.containsKey(KAFKA_OAUTH_SERVER_CLIENT_SECRET)) { 132 | defaultClientSecret = prop.getProperty(KAFKA_OAUTH_SERVER_CLIENT_SECRET); 133 | } 134 | 135 | this.clientSecret = EnvironmentVariablesUtil.getStringEnvironmentVariable( 136 | KAFKA_OAUTH_SERVER_CLIENT_SECRET_ENV_VAR, 137 | defaultClientSecret); 138 | 139 | // get the OAuth server grant type 140 | log.debug("Configure the OAuth server grant type"); 141 | String defaultGrantType = "client_credentials"; 142 | if (prop.containsKey(KAFKA_OAUTH_SERVER_GRANT_TYPE)) { 143 | defaultGrantType = prop.getProperty(KAFKA_OAUTH_SERVER_GRANT_TYPE); 144 | } 145 | 146 | this.grantType = EnvironmentVariablesUtil.getStringEnvironmentVariable( 147 | KAFKA_OAUTH_SERVER_GRANT_TYPE_ENV_VAR, 148 | defaultGrantType); 149 | 150 | // get the OAuth server scopes 151 | log.debug("Configure the OAuth server scopes"); 152 | String defaultScopes = null; 153 | if (prop.containsKey(KAFKA_OAUTH_SERVER_SCOPES)) { 154 | defaultScopes = prop.getProperty(KAFKA_OAUTH_SERVER_SCOPES); 155 | } 156 | 157 | this.scopes = EnvironmentVariablesUtil.getStringEnvironmentVariable( 158 | KAFKA_OAUTH_SERVER_SCOPES_ENV_VAR, 159 | defaultScopes); 160 | 161 | // get the OAuth server unsecure server 162 | log.debug("Configure the OAuth server unsecure server"); 163 | Boolean defaultUnsecureServer = false; 164 | if (prop.containsKey(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER)) { 165 | String propValue = prop.getProperty(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER); 166 | 167 | defaultUnsecureServer = Boolean.valueOf(propValue); 168 | } 169 | 170 | this.unsecureServer = EnvironmentVariablesUtil.getBooleanEnvironmentVariable( 171 | KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER_ENV_VAR, 172 | defaultUnsecureServer); 173 | 174 | if (!this.isValid()) { 175 | throw new IllegalStateException("Configuration entries are invalid."); 176 | } 177 | 178 | } catch (Exception ex) { 179 | log.error("Error getting OAuth configuration, Message: %s", ex); 180 | throw ex; 181 | } finally { 182 | log.debug("Finished initializing OAuth configuration"); 183 | } 184 | } 185 | 186 | //endregion 187 | 188 | //region Public Properties 189 | 190 | /** 191 | * Gets base server uri. 192 | * 193 | * @return the base server uri 194 | */ 195 | public String getBaseServerUri() { 196 | return this.baseServerUri; 197 | } 198 | 199 | /** 200 | * Gets token endpoint path. 201 | * 202 | * @return the token endpoint path 203 | */ 204 | public String getTokenEndpointPath() { 205 | return tokenEndpointPath; 206 | } 207 | 208 | 209 | /** 210 | * Gets token endpoint. 211 | * 212 | * @return the token endpoint 213 | */ 214 | public String getTokenEndpoint() { 215 | return this.baseServerUri + this.tokenEndpointPath; 216 | } 217 | 218 | /** 219 | * Gets introspection endpoint. 220 | * 221 | * @return the introspection endpoint 222 | */ 223 | public String getIntrospectionEndpoint() { 224 | return this.baseServerUri + this.introspectionEndpointPath; 225 | } 226 | 227 | /** 228 | * Gets introspection endpoint path. 229 | * 230 | * @return the introspection endpoint path 231 | */ 232 | public String getIntrospectionEndpointPath() { 233 | return this.introspectionEndpointPath; 234 | } 235 | 236 | /** 237 | * Gets client id. 238 | * 239 | * @return the client id 240 | */ 241 | public String getClientId() { 242 | return this.clientId; 243 | } 244 | 245 | 246 | /** 247 | * Gets client secret. 248 | * 249 | * @return the client secret 250 | */ 251 | public String getClientSecret() { 252 | return this.clientSecret; 253 | } 254 | 255 | 256 | /** 257 | * Gets grant type. 258 | * 259 | * @return the grant type 260 | */ 261 | public String getGrantType() { 262 | return this.grantType; 263 | } 264 | 265 | 266 | /** 267 | * Gets scopes. 268 | * 269 | * @return the scopes 270 | */ 271 | public String getScopes() { 272 | return this.scopes; 273 | } 274 | 275 | /** 276 | * Gets unsecure server. 277 | * 278 | * @return the unsecure server 279 | */ 280 | public Boolean getUnsecureServer() { 281 | return this.unsecureServer; 282 | } 283 | 284 | 285 | //endregion 286 | 287 | //region Public Methods 288 | 289 | /** 290 | * Is valid boolean. 291 | * 292 | * @return the boolean 293 | */ 294 | private Boolean isValid() { 295 | 296 | if (!Utils.isURIValid(this.baseServerUri)) { 297 | // the token endpoint is not valid 298 | return false; 299 | } 300 | 301 | if (!Utils.isURIValid(this.baseServerUri + this.tokenEndpointPath)) { 302 | // the token endpoint is not valid 303 | return false; 304 | } 305 | 306 | if (!Utils.isURIValid(this.baseServerUri + this.introspectionEndpointPath)) { 307 | // the introspection endpoint is not valid 308 | return false; 309 | } 310 | 311 | if (Utils.isNullOrEmpty(this.clientId)) { 312 | return false; 313 | } 314 | 315 | if (Utils.isNullOrEmpty(this.clientSecret)) { 316 | return false; 317 | } 318 | 319 | if (Utils.isNullOrEmpty(this.grantType)) { 320 | return false; 321 | } 322 | 323 | return true; 324 | } 325 | 326 | public void setConfigurationFromJaasConfigEntries(Map jaasConfigEntries) { 327 | //validate the parameters 328 | Objects.requireNonNull(jaasConfigEntries); 329 | 330 | // get the OAuth server base Uri 331 | String defaultBaseServerUri = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_BASE_URI, ""); 332 | if (!Utils.isNullOrEmpty(defaultBaseServerUri)) { 333 | this.baseServerUri = defaultBaseServerUri; 334 | } 335 | 336 | // get the OAuth server token path 337 | String defaultTokenEndpointPath = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH, ""); 338 | if (!Utils.isNullOrEmpty(defaultTokenEndpointPath)) { 339 | this.tokenEndpointPath = defaultTokenEndpointPath; 340 | } 341 | 342 | // get the OAuth server introspection endpoint path 343 | String defaultIntrospectionEndpointPath = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH, ""); 344 | if (!Utils.isNullOrEmpty(defaultIntrospectionEndpointPath)) { 345 | this.introspectionEndpointPath = defaultIntrospectionEndpointPath; 346 | } 347 | 348 | // get the OAuth server client id 349 | String defaultClientId = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_CLIENT_ID, ""); 350 | if (!Utils.isNullOrEmpty(defaultClientId)) { 351 | this.clientId = defaultClientId; 352 | } 353 | 354 | // get the OAuth server client secret 355 | String defaultClientSecret = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_CLIENT_SECRET, ""); 356 | if (!Utils.isNullOrEmpty(defaultClientSecret)) { 357 | this.clientSecret = defaultClientSecret; 358 | } 359 | 360 | // get the OAuth server grant type 361 | String defaultGrantType = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_GRANT_TYPE, ""); 362 | if (!Utils.isNullOrEmpty(defaultGrantType)) { 363 | this.grantType = defaultGrantType; 364 | } 365 | 366 | // get the OAuth server scopes 367 | String defaultScopes = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_SCOPES, ""); 368 | if (!Utils.isNullOrEmpty(defaultScopes)) { 369 | this.scopes = defaultScopes; 370 | } 371 | 372 | // get the OAuth server unsecure server 373 | String defaultUnsecureServer = jaasConfigEntries.getOrDefault(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER, ""); 374 | if (!Utils.isNullOrEmpty(defaultUnsecureServer)) { 375 | this.unsecureServer = Boolean.valueOf(defaultUnsecureServer); 376 | } 377 | 378 | //check if the configuration remains valid 379 | if (!this.isValid()) { 380 | throw new IllegalStateException("Configuration entries at jaas configuration file are invalid."); 381 | } 382 | } 383 | 384 | //endregion 385 | 386 | //region Protected Methods 387 | 388 | 389 | /** 390 | * Gets configuration file properties. 391 | * 392 | * @return the configuration file properties 393 | */ 394 | protected Properties getConfigurationFileProperties() { 395 | 396 | Properties properties = new Properties(); 397 | try { 398 | log.debug("Starting to load configuration properties from property file."); 399 | 400 | String configurationFilePath = EnvironmentVariablesUtil.getStringEnvironmentVariable( 401 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR, 402 | null); 403 | 404 | if (!Utils.isNullOrEmpty(configurationFilePath)) { 405 | log.debug( 406 | "Load properties from {}", 407 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR); 408 | 409 | InputStream input = new FileInputStream(configurationFilePath); 410 | 411 | // load a properties file 412 | properties.load(input); 413 | } else { 414 | properties.setProperty(KAFKA_OAUTH_SERVER_BASE_URI, "http://localhost:8080/auth/realms/master/protocol/openid-connect"); 415 | properties.setProperty(KAFKA_OAUTH_SERVER_TOKEN_ENDPOINT_PATH, "/token"); 416 | properties.setProperty(KAFKA_OAUTH_SERVER_INTROSPECTION_ENDPOINT_PATH, "/token/introspect"); 417 | properties.setProperty(KAFKA_OAUTH_SERVER_CLIENT_ID, "test-consumer"); 418 | properties.setProperty(KAFKA_OAUTH_SERVER_CLIENT_SECRET, "7b3c23ef-8909-489e-bf4a-64a84abb197e"); 419 | properties.setProperty(KAFKA_OAUTH_SERVER_GRANT_TYPE, "client_credentials"); 420 | properties.setProperty(KAFKA_OAUTH_SERVER_SCOPES, "test"); 421 | properties.setProperty(KAFKA_OAUTH_SERVER_ACCEPT_UNSECURE_SERVER, "true"); 422 | 423 | log.debug( 424 | "{} enviroment variable is not set, cannot load properties.", 425 | KAFKA_OAUTH_SERVER_PROP_FILE_ENV_VAR); 426 | } 427 | }catch (FileNotFoundException ex) { 428 | String errMsg = String.format("OAuth property file not found, Message: %s", ex.getMessage()); 429 | log.warn(errMsg); 430 | } catch (IOException ex) { 431 | String errMsg = String.format("Error reading OAuth property file not found, Message: %s", ex.getMessage()); 432 | log.warn(errMsg); 433 | } finally { 434 | log.debug("Finished loading configuration properties from property file."); 435 | } 436 | 437 | return properties; 438 | } 439 | 440 | //endregion 441 | } 442 | 443 | 444 | 445 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthScope.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | /** 19 | * The type O auth scope. 20 | */ 21 | public class OAuthScope { 22 | private String resourceType; 23 | private String resourceName; 24 | private String operation; 25 | 26 | /** 27 | * Instantiates a new O auth scope. 28 | */ 29 | public OAuthScope() { 30 | 31 | } 32 | 33 | /** 34 | * Gets resource type. 35 | * 36 | * @return the resource type 37 | */ 38 | public String getResourceType() { 39 | return resourceType; 40 | } 41 | 42 | /** 43 | * Sets resource type. 44 | * 45 | * @param resourceType the resource type 46 | */ 47 | public void setResourceType(String resourceType) { 48 | this.resourceType = resourceType; 49 | } 50 | 51 | /** 52 | * Gets resource name. 53 | * 54 | * @return the resource name 55 | */ 56 | public String getResourceName() { 57 | return resourceName; 58 | } 59 | 60 | /** 61 | * Sets resource name. 62 | * 63 | * @param resourceName the resource name 64 | */ 65 | public void setResourceName(String resourceName) { 66 | this.resourceName = resourceName; 67 | } 68 | 69 | /** 70 | * Gets operation. 71 | * 72 | * @return the operation 73 | */ 74 | public String getOperation() { 75 | return operation; 76 | } 77 | 78 | /** 79 | * Sets operation. 80 | * 81 | * @param operation the operation 82 | */ 83 | public void setOperation(String operation) { 84 | this.operation = operation; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthService.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import java.io.IOException; 19 | import java.util.Map; 20 | 21 | /** 22 | * This interface defines services from an OAuth Server that are needed for lib-kafka-oauth 23 | */ 24 | public interface OAuthService { 25 | 26 | /** 27 | * Request an access token for an OAuth client from an OAuth Server 28 | * 29 | * @return the o auth bearer token jwt 30 | */ 31 | OAuthBearerTokenJwt requestAccessToken() throws IOException; 32 | 33 | /** 34 | * Validate an access token in an OAuth Server 35 | * 36 | * @param accessToken the access token 37 | * @return the o auth bearer token jwt 38 | */ 39 | OAuthBearerTokenJwt validateAccessToken(String accessToken) throws IOException; 40 | 41 | /** 42 | * Gets configurations that are needed to connect to an OAuth server 43 | * 44 | * @return the o auth configuration 45 | */ 46 | OAuthConfiguration getOAuthConfiguration(); 47 | 48 | void setOAuthConfiguration(Map jaasConfigEntries); 49 | } -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/OAuthServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.utils.Time; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.io.DataOutputStream; 23 | import java.io.IOException; 24 | import java.net.HttpURLConnection; 25 | import java.net.URL; 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.Map; 28 | import java.util.Objects; 29 | 30 | /** 31 | * The class that handles the logic to interact with the OAuth server. 32 | */ 33 | public class OAuthServiceImpl implements OAuthService { 34 | 35 | //region Constants 36 | 37 | public static final String OAUTH_GRANT_TYPE = "grant_type"; 38 | public static final String OAUTH_SCOPE = "scope"; 39 | public static final String OAUTH_ACCESS_TOKEN = "access_token"; 40 | public static final String OAUTH_ACCESS_TOKEN_EXPIRES_IN = "expires_in"; 41 | 42 | //endregion 43 | 44 | //region Member Variables 45 | 46 | private static final Logger log = LoggerFactory.getLogger(OAuthServiceImpl.class); 47 | private OAuthConfiguration oauthConfiguration; 48 | private static Time time = Time.SYSTEM; 49 | 50 | //endregion 51 | 52 | //region Constructors 53 | 54 | /** 55 | * Instantiates a new O auth service. 56 | */ 57 | public OAuthServiceImpl() { 58 | this.oauthConfiguration = new OAuthConfiguration(); 59 | } 60 | 61 | //endregion 62 | 63 | //region Public Properties 64 | 65 | public OAuthConfiguration getOAuthConfiguration() { 66 | return this.oauthConfiguration; 67 | } 68 | 69 | @Override 70 | public void setOAuthConfiguration(Map jaasConfigEntries) { 71 | try { 72 | //validate parameters 73 | Objects.requireNonNull(jaasConfigEntries); 74 | this.oauthConfiguration.setConfigurationFromJaasConfigEntries(jaasConfigEntries); 75 | } catch (RuntimeException e) { 76 | log.warn("Error on trying to configure oauth using jaas configuration entries. Using environment or properties file configuration"); 77 | } 78 | } 79 | 80 | //endregion 81 | 82 | //region Public Methods 83 | 84 | /** 85 | * @return OAuthBearerTokenJwt which has accessToken string as an attribute of the object 86 | */ 87 | /** 88 | * This method gets a JWT token from an OAuth Server. 89 | * The JWT token contains the access token string. 90 | * @return a JWT token if client ID exist in the OAuth Server or null if not exist in the OAuth Server 91 | * @throws IOException - if Call to OAuth Server fails 92 | */ 93 | public OAuthBearerTokenJwt requestAccessToken() throws IOException { 94 | OAuthBearerTokenJwt result = null; 95 | log.debug("Starting to request access token from OAuth server."); 96 | 97 | String clientId = this.oauthConfiguration.getClientId(); 98 | 99 | // initialize the time of the request 100 | long callTime = time.milliseconds(); 101 | 102 | // setup post parameters 103 | String grantType = String.format("%s=%s", OAUTH_GRANT_TYPE, this.oauthConfiguration.getGrantType()); 104 | String scope = String.format("%s=%s", OAUTH_SCOPE, this.oauthConfiguration.getScopes()); 105 | String postParameters = String.format("%s&%s", grantType, scope); 106 | 107 | log.info("Send access token request to the OAuth server."); 108 | Map resp = doHttpCall( 109 | this.oauthConfiguration.getTokenEndpoint(), 110 | postParameters, 111 | Utils.createBasicAuthorizationHeader(clientId, this.oauthConfiguration.getClientSecret())); 112 | 113 | // check to see if the response is not null 114 | if (resp != null) { 115 | // create a new token from the response 116 | log.debug("Access token response is not null, create an token."); 117 | String accessToken = (String) resp.get(OAUTH_ACCESS_TOKEN); 118 | long expiresIn = ((Integer) resp.get(OAUTH_ACCESS_TOKEN_EXPIRES_IN)).longValue(); 119 | result = new OAuthBearerTokenJwt(accessToken, expiresIn, callTime, clientId); 120 | } else { 121 | log.error("Error requesting access token from OAuth server, the HTTP response was null."); 122 | } 123 | log.debug("Finished requesting access token from OAuth server."); 124 | return result; 125 | } 126 | 127 | /** 128 | * This method vaidates an access token string in the OAuth Server 129 | * @param accessToken the access token string 130 | * @return a JWT token if accessToken exists in the OAuth Server, or null if not exist in the OAuth Server 131 | * @throws IOException - if Call to OAuth Server fails 132 | */ 133 | public OAuthBearerTokenJwt validateAccessToken(String accessToken) throws IOException { 134 | OAuthBearerTokenJwt result = null; 135 | log.debug("Starting to validate access token against OAuth server."); 136 | 137 | // check parameters 138 | log.debug("Validate method parameters."); 139 | Objects.requireNonNull(accessToken); 140 | 141 | // create post parameters 142 | String token = "token=" + accessToken; 143 | 144 | // validate the access token by calling the oauth introspection endpoint 145 | log.debug("Validate the access token by calling the OAuth introspection endpoint."); 146 | Map resp = doHttpCall( 147 | this.oauthConfiguration.getIntrospectionEndpoint(), 148 | token, 149 | Utils.createBasicAuthorizationHeader(this.oauthConfiguration.getClientId(), this.oauthConfiguration.getClientSecret())); 150 | 151 | // check to see if the response is not null - accessToken exists in the OAuth Server 152 | if (resp != null) { 153 | // check to see if the access token is still active 154 | log.debug("Validation response was not null check to see if the access token is active."); 155 | boolean active = (boolean) resp.get("active"); 156 | 157 | if (active) { 158 | // the access token is still active create a new token with the response 159 | log.debug("Access token is still active create a new token with the response."); 160 | result = new OAuthBearerTokenJwt(resp, accessToken); 161 | } else { 162 | // the access token is no longer active 163 | String errMsg = String.format("Access token has expired."); 164 | log.error(errMsg); 165 | } 166 | } else { // accessToken does not exist in the OAuth Server 167 | // the http response was null, cannot validate access token 168 | String errMsg = "Error validating access token against OAuth server, the HTTP response was null."; 169 | log.error(errMsg); 170 | } 171 | log.debug("Finished validating access token against OAuth server."); 172 | return result; 173 | } 174 | 175 | //endregion 176 | 177 | //region Protected Methods 178 | 179 | /** 180 | * Do http call to the OAuth Server. 181 | * @param urlStr OAuth Server URL 182 | * @param postParameters 183 | * @param authorizationHeaderValue 184 | * @return null if HTTP response code is not 200 185 | * @throws IOException 186 | */ 187 | protected Map doHttpCall(String urlStr, String postParameters, String authorizationHeaderValue) throws IOException { 188 | log.debug(String.format("Starting to make HTTP call, Url: %s.", urlStr)); 189 | 190 | // check parameters 191 | log.debug("Validate method parameters."); 192 | Objects.requireNonNull(urlStr); 193 | Objects.requireNonNull(postParameters); 194 | //Objects.requireNonNull(authorizationHeaderValue); 195 | 196 | // configure SSL context to allow unsecured connections if configured 197 | log.debug("Configure SSL context to allow unsecured connections if configured."); 198 | Utils.acceptUnsecureServer(this.oauthConfiguration.getUnsecureServer()); 199 | 200 | log.debug(String.format("Send POST request, Url: %s.", urlStr)); 201 | byte[] postData = postParameters.getBytes(StandardCharsets.UTF_8); 202 | int postDataLength = postData.length; 203 | 204 | URL url = new URL(urlStr); 205 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); 206 | con.setInstanceFollowRedirects(true); 207 | con.setRequestMethod("POST"); 208 | con.setRequestProperty("Authorization", authorizationHeaderValue); 209 | con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 210 | con.setRequestProperty("charset", "utf-8"); 211 | con.setRequestProperty("Content-Length", Integer.toString(postDataLength)); 212 | con.setUseCaches(false); 213 | con.setDoOutput(true); 214 | 215 | try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { 216 | wr.write(postData); 217 | } 218 | 219 | log.debug(String.format("Get HTTP response code, Url: %s.", urlStr)); 220 | int responseCode = con.getResponseCode(); 221 | 222 | // check to see if the response was successful 223 | log.debug(String.format("Check to see if the response was successful, Url: %s.", urlStr)); 224 | if (responseCode == 200) { 225 | // the response was successful, parse to json into a key value pairs 226 | log.debug("The response was successful, parse to json into a key value pairs, Url: {}.", urlStr); 227 | return Utils.handleJsonResponse(con.getInputStream()); 228 | } else { 229 | // the response was not successful 230 | String errMsg = String.format( 231 | "The response was not successful, Url: %s, Response Code: %s", 232 | urlStr, 233 | responseCode); 234 | 235 | log.error(errMsg); 236 | return null; 237 | 238 | } 239 | } 240 | 241 | //endregion 242 | } 243 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/java/com/bfm/kafka/security/oauthbearer/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import com.fasterxml.jackson.core.type.TypeReference; 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import javax.net.ssl.HttpsURLConnection; 24 | import javax.net.ssl.SSLContext; 25 | import javax.net.ssl.TrustManager; 26 | import javax.net.ssl.X509TrustManager; 27 | import java.io.BufferedReader; 28 | import java.io.InputStream; 29 | import java.io.InputStreamReader; 30 | import java.net.URI; 31 | import java.net.URISyntaxException; 32 | import java.security.KeyManagementException; 33 | import java.security.NoSuchAlgorithmException; 34 | import java.util.Base64; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.Objects; 38 | 39 | /** 40 | * The type Utils. 41 | */ 42 | public class Utils { 43 | private static final Logger log = LoggerFactory.getLogger(Utils.class); 44 | 45 | /** 46 | * Is uri valid boolean. 47 | * 48 | * @param str the str 49 | * @return the boolean 50 | */ 51 | public static Boolean isURIValid(String str) { 52 | try { 53 | URI uri = new URI(str); 54 | } catch (NullPointerException e) { 55 | // If str is null 56 | return false; 57 | 58 | } catch (URISyntaxException e) { 59 | // str is not valid 60 | return false; 61 | } 62 | 63 | // the str is valid uri 64 | return true; 65 | } 66 | 67 | /** 68 | * Is null or empty boolean. 69 | * 70 | * @param str the str 71 | * @return the boolean 72 | */ 73 | public static Boolean isNullOrEmpty(String str) { 74 | if ((str == null) || (str.isEmpty())) { 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | /** 81 | * Create basic authorization header string. 82 | * 83 | * @param clientId the client id 84 | * @param clientSecret the client secret 85 | * @return the string 86 | */ 87 | protected static String createBasicAuthorizationHeader(String clientId, String clientSecret) { 88 | try { 89 | log.debug("Starting to create basic authorization header value."); 90 | 91 | String usernameAndPassword = String.format( 92 | "%s:%s", 93 | clientId, 94 | clientSecret); 95 | 96 | String base64UsernameAndPassword = Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); 97 | 98 | String basicAuthHeader = String.format("Basic %s", base64UsernameAndPassword); 99 | 100 | return basicAuthHeader; 101 | } catch (Exception ex) { 102 | String errMsg = String.format( 103 | "Error creating basic authorization header value., Message: %s", 104 | ex.getMessage()); 105 | 106 | log.error(errMsg, ex); 107 | 108 | throw ex; 109 | } finally { 110 | log.debug("Finished creating basic authorization header value."); 111 | } 112 | } 113 | 114 | /** 115 | * Handle json response map. 116 | * 117 | * @param inputStream the input stream 118 | * @return the map 119 | */ 120 | protected static Map handleJsonResponse(InputStream inputStream) { 121 | // initialize the result 122 | Map result = null; 123 | try { 124 | log.debug("Starting to convert HTTP JSON response into a key value pairs."); 125 | 126 | // check parameters 127 | log.debug("Validate method parameters."); 128 | Objects.requireNonNull(inputStream); 129 | 130 | // read the response into a string 131 | log.debug("Read the HTTP response into a string."); 132 | BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); 133 | String inputLine; 134 | StringBuilder response = new StringBuilder(); 135 | 136 | while ((inputLine = in.readLine()) != null) { 137 | response.append(inputLine); 138 | } 139 | in.close(); 140 | 141 | String jsonResponse = response.toString(); 142 | 143 | // parse the response into a key value pairs 144 | log.debug("Parse JSON string into a key value pairs."); 145 | ObjectMapper objectMapper = new ObjectMapper(); 146 | result = objectMapper.readValue(jsonResponse, new TypeReference>() { 147 | }); 148 | } catch (NullPointerException npe) { 149 | log.error("Error converting HTTP JSON response, null pointer exception, Message: {}.", npe.getMessage()); 150 | throw npe; 151 | } catch (Exception ex) { 152 | log.error("Error converting HTTP JSON response into a key value pairs, Message: {}.", ex.getMessage()); 153 | } finally { 154 | log.debug("Finished converting HTTP JSON response into a key value pairs"); 155 | } 156 | return result; 157 | } 158 | 159 | /** 160 | * Handle array response list. 161 | * 162 | * @param inputStream the input stream 163 | * @return the list 164 | */ 165 | protected static List handleArrayResponse(InputStream inputStream) { 166 | // initialize the result 167 | Listresult = null; 168 | try { 169 | 170 | // check parameters 171 | log.debug("Validate method parameters."); 172 | Objects.requireNonNull(inputStream); 173 | 174 | // read the response into a string 175 | log.debug("Read the HTTP response into a string."); 176 | BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); 177 | String inputLine; 178 | StringBuilder response = new StringBuilder(); 179 | 180 | while ((inputLine = in.readLine()) != null) { 181 | response.append(inputLine); 182 | } 183 | in.close(); 184 | 185 | String jsonResponse = response.toString(); 186 | 187 | ObjectMapper objectMapper = new ObjectMapper(); 188 | result = objectMapper.readValue(jsonResponse, new TypeReference>() { 189 | }); 190 | } catch (NullPointerException npe) { 191 | log.error("Error converting HTTP JSON response, null pointer exception, Message: {}.", npe.getMessage()); 192 | throw npe; 193 | } catch (Exception ex) { 194 | log.error("Error converting HTTP JSON response into a key value pairs, Message: {}.", ex.getMessage()); 195 | } finally { 196 | log.debug("Finished converting HTTP JSON response into a key value pairs"); 197 | } 198 | return result; 199 | } 200 | 201 | /** 202 | * Accept unsecure server. 203 | * 204 | * @param unsecure the unsecure 205 | */ 206 | protected static void acceptUnsecureServer(boolean unsecure) { 207 | try { 208 | log.debug("Starting to enable unsecure HTTP connections."); 209 | 210 | if (!unsecure) { 211 | // unsecured servers are not allowed 212 | return; 213 | } 214 | 215 | // configure SSL context to allow unsecured servers 216 | TrustManager[] trustAllCerts = new TrustManager[]{ 217 | new X509TrustManager() { 218 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 219 | return null; 220 | } 221 | 222 | public void checkClientTrusted( 223 | java.security.cert.X509Certificate[] certs, String authType) { 224 | } 225 | 226 | public void checkServerTrusted( 227 | java.security.cert.X509Certificate[] certs, String authType) { 228 | } 229 | } 230 | }; 231 | 232 | 233 | SSLContext sc = SSLContext.getInstance("SSL"); 234 | sc.init(null, trustAllCerts, new java.security.SecureRandom()); 235 | HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 236 | HttpsURLConnection.setDefaultHostnameVerifier ((hostname, session) -> true); 237 | } catch (NoSuchAlgorithmException e) { 238 | String errMsg = String.format("Error enabling unsecure HTTP connections, Message: %s", e.getMessage()); 239 | log.error(errMsg, e); 240 | } catch (KeyManagementException e) { 241 | String errMsg = String.format("Error enabling unsecure HTTP connections, Message: %s", e.getMessage()); 242 | log.error(errMsg, e); 243 | } catch (Exception e) { 244 | String errMsg = String.format("Error enabling unsecure HTTP connections, Message: %s", e.getMessage()); 245 | log.error(errMsg, e); 246 | } finally { 247 | log.debug("Finished enabling unsecure HTTP connections."); 248 | } 249 | } 250 | 251 | /** 252 | * Create bearer header string. 253 | * 254 | * @param accessToken the access token 255 | * @return the string 256 | */ 257 | public static String createBearerHeader(String accessToken) { 258 | return "Bearer " + accessToken; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 0.0.1 2 | -------------------------------------------------------------------------------- /kafka-oauth/src/main/resources/oauth-configuration.properties: -------------------------------------------------------------------------------- 1 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 2 | oauth.server.token.endpoint.path=/token 3 | oauth.server.introspection.endpoint.path=/token/introspect 4 | oauth.server.client.id=test-consumer 5 | oauth.server.client.secret=7b3c23ef-8909-489e-bf4a-64a84abb197e 6 | oauth.server.grant.type=client_credentials 7 | oauth.server.scopes=test 8 | oauth.server.accept.unsecure.server=true -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/CustomAuthorizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import kafka.network.RequestChannel; 19 | import kafka.security.auth.Operation; 20 | import kafka.security.auth.Resource; 21 | import kafka.security.auth.ResourceType; 22 | import org.apache.kafka.common.acl.AclOperation; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.InjectMocks; 27 | import org.mockito.Mock; 28 | import org.mockito.Mockito; 29 | import org.mockito.MockitoAnnotations; 30 | import org.mockito.junit.MockitoJUnitRunner; 31 | 32 | import java.util.ArrayList; 33 | import java.util.HashSet; 34 | import java.util.List; 35 | import java.util.Set; 36 | 37 | import static org.junit.Assert.assertTrue; 38 | @RunWith(MockitoJUnitRunner.class) 39 | public class CustomAuthorizerTest { 40 | 41 | @Mock 42 | RequestChannel.Session session; 43 | 44 | @Mock 45 | Operation operation; 46 | 47 | @Mock 48 | Resource resource; 49 | 50 | @Mock 51 | OAuthBearerTokenJwt jwt; 52 | 53 | @Mock 54 | CustomPrincipal customPrincipal; 55 | 56 | @InjectMocks 57 | CustomAuthorizer customAuthorizer; 58 | 59 | @Before 60 | public void initMocks() { 61 | MockitoAnnotations.initMocks(this); 62 | } 63 | 64 | @Test 65 | public void authorize() { 66 | Mockito.when(session.principal()).thenReturn(customPrincipal); 67 | Mockito.when(customPrincipal.getOauthBearerTokenJwt()).thenReturn(jwt); 68 | 69 | Set set = new HashSet<>(); 70 | set.add("urn:kafka:topic:test:write"); 71 | 72 | Mockito.when(jwt.scope()).thenReturn(set); 73 | Mockito.when(resource.name()).thenReturn("test"); 74 | Mockito.when(resource.resourceType()).thenReturn(ResourceType.fromString("topic")); 75 | Mockito.when(operation.toJava()).thenReturn(AclOperation.fromString("write")); 76 | boolean result = customAuthorizer.authorize(session, operation, resource); 77 | 78 | assertTrue(result); 79 | } 80 | 81 | @Test 82 | public void checkAuthorization() { 83 | List list = new ArrayList<>(); 84 | OAuthScope scope = new OAuthScope(); 85 | scope.setOperation("Write"); 86 | scope.setResourceName("test"); 87 | scope.setResourceType("topic"); 88 | list.add(scope); 89 | 90 | Resource resource = new Resource(ResourceType.fromString("Topic"), "TEST"); 91 | 92 | CustomAuthorizer authorizer = new CustomAuthorizer(); 93 | 94 | boolean result = authorizer.checkAuthorization(list, resource, "write"); 95 | 96 | assertTrue(result); 97 | 98 | } 99 | 100 | @Test 101 | public void parseScopes() { 102 | Set set = new HashSet<>(); 103 | set.add("urn:kafka:topic:test:write"); 104 | set.add("urn:kafka:group:test:read"); 105 | CustomAuthorizer authorizer = new CustomAuthorizer(); 106 | List scopes = authorizer.parseScopes(set); 107 | 108 | assertTrue(scopes.size() == 2); 109 | assertTrue(scopes.get(0).getOperation().equals("write")); 110 | assertTrue(scopes.get(0).getResourceName().equals("test")); 111 | assertTrue(scopes.get(0).getResourceType().equals("topic")); 112 | } 113 | 114 | @Test 115 | public void parseBadScope() { 116 | Set set = new HashSet<>(); 117 | set.add("urn:test:write"); 118 | CustomAuthorizer authorizer = new CustomAuthorizer(); 119 | List scopes = authorizer.parseScopes(set); 120 | 121 | assertTrue(scopes.size() == 0); 122 | } 123 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/CustomPrincipalBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.KafkaException; 19 | import org.apache.kafka.common.security.auth.SaslAuthenticationContext; 20 | import org.apache.kafka.common.security.auth.SslAuthenticationContext; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.mockito.InjectMocks; 25 | import org.mockito.Mock; 26 | import org.mockito.Mockito; 27 | import org.mockito.MockitoAnnotations; 28 | import org.mockito.junit.MockitoJUnitRunner; 29 | 30 | import javax.security.sasl.SaslServer; 31 | 32 | import static org.junit.Assert.assertTrue; 33 | @RunWith(MockitoJUnitRunner.class) 34 | public class CustomPrincipalBuilderTest { 35 | 36 | @Mock 37 | SaslAuthenticationContext saslAuthenticationContext; 38 | 39 | @Mock 40 | SslAuthenticationContext sslAuthenticationContext; 41 | 42 | @Mock 43 | SaslServer saslServer; 44 | 45 | @InjectMocks 46 | CustomPrincipalBuilder customPrincipalBuilder; 47 | 48 | @Before 49 | public void initMocks() { 50 | MockitoAnnotations.initMocks(this); 51 | } 52 | 53 | 54 | @Test 55 | public void build() { 56 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt("token", 1, 1, "User"); 57 | Mockito.when(saslAuthenticationContext.server()).thenReturn(saslServer); 58 | Mockito.when(saslServer.getNegotiatedProperty("OAUTHBEARER.token")).thenReturn(jwt); 59 | CustomPrincipal customPrincipal = customPrincipalBuilder.build(saslAuthenticationContext); 60 | 61 | assertTrue(customPrincipal != null); 62 | } 63 | 64 | @Test(expected = KafkaException.class) 65 | public void buildThrowException() { 66 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt("token", 1, 1, "User"); 67 | Mockito.when(saslAuthenticationContext.server()).thenReturn(null); 68 | 69 | CustomPrincipal customPrincipal = customPrincipalBuilder.build(saslAuthenticationContext); 70 | } 71 | 72 | @Test(expected = KafkaException.class) 73 | public void buildBadType() { 74 | CustomPrincipal customPrincipal = customPrincipalBuilder.build(sslAuthenticationContext); 75 | } 76 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/CustomPrincipalTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertTrue; 21 | 22 | public class CustomPrincipalTest { 23 | 24 | @Test 25 | public void testGettersSetters() { 26 | CustomPrincipal customPrincipal = new CustomPrincipal("User", "TEST"); 27 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt("token", 1, 1, "User"); 28 | customPrincipal.setOauthBearerTokenJwt(jwt); 29 | 30 | assertTrue(customPrincipal.getOauthBearerTokenJwt().equals(jwt)); 31 | assertTrue(customPrincipal.getPrincipalType().equals("User")); 32 | assertTrue(customPrincipal.getName().equals("TEST")); 33 | } 34 | 35 | @Test 36 | public void toStringTest() { 37 | CustomPrincipal customPrincipal = new CustomPrincipal("User", "TEST"); 38 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt("token", 1, 1, "User"); 39 | customPrincipal.setOauthBearerTokenJwt(jwt); 40 | 41 | assertTrue(customPrincipal.toString().equals("CustomPrincipal{oauthBearerTokenJwt=OauthBearerTokenJwt {value='token', lifetimeMs=1001, " + 42 | "principalName='User', startTimeMs=1, scope=null, expirationTime=1001, jti='null'}} User:TEST")); 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/EnvironmentVariablesUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | //package com.bfm.kafka.security.oauthbearer; 17 | // 18 | //import org.junit.Test; 19 | // 20 | //import java.lang.reflect.Field; 21 | //import java.util.Collections; 22 | //import java.util.HashMap; 23 | //import java.util.Map; 24 | // 25 | //import static org.junit.Assert.assertTrue; 26 | // 27 | ///** 28 | // * The type Environment variables util test. 29 | // */ 30 | //public class EnvironmentVariablesUtilTest { 31 | // 32 | // /** 33 | // * Gets integer environment variable. 34 | // * 35 | // * @throws Exception the exception 36 | // */ 37 | // @Test 38 | // public void getIntegerEnvironmentVariable() throws Exception { 39 | // Map map = new HashMap<>(); 40 | // map.put("getIntegerEnvironmentVariable", "2"); 41 | // setEnv(map); 42 | // Integer output = EnvironmentVariablesUtil.getIntegerEnvironmentVariable("getIntegerEnvironmentVariable", Integer.valueOf(1)); 43 | // assertTrue(output == 2); 44 | // } 45 | // 46 | // /** 47 | // * Gets integer environment variable. 48 | // * 49 | // * @throws Exception the exception 50 | // */ 51 | // @Test 52 | // public void getIntegerNullEnvironmentVariable() throws Exception { 53 | // Map map = new HashMap<>(); 54 | // map.put("getIntegerNullEnvironmentVariable", null); 55 | // setEnv(map); 56 | // Integer output = EnvironmentVariablesUtil.getIntegerEnvironmentVariable("getIntegerNullEnvironmentVariable", Integer.valueOf(1)); 57 | // assertTrue(output == 1); 58 | // } 59 | // 60 | // /** 61 | // * Gets float environment variable. 62 | // * 63 | // * @throws Exception the exception 64 | // */ 65 | // @Test 66 | // public void getFloatEnvironmentVariable() throws Exception{ 67 | // Map map = new HashMap<>(); 68 | // map.put("getFloatEnvironmentVariable", "2.0f"); 69 | // setEnv(map); 70 | // Float output = EnvironmentVariablesUtil.getFloatEnvironmentVariable("getFloatEnvironmentVariable", Float.valueOf(1.0f)); 71 | // assertTrue(output == 2.0f); 72 | // } 73 | // 74 | // /** 75 | // * Gets float environment variable. 76 | // * 77 | // * @throws Exception the exception 78 | // */ 79 | // @Test 80 | // public void getFloatNullEnvironmentVariable() throws Exception{ 81 | // Map map = new HashMap<>(); 82 | // map.put("getFloatEnvironmentVariable", null); 83 | // setEnv(map); 84 | // Float output = EnvironmentVariablesUtil.getFloatEnvironmentVariable("getFloatEnvironmentVariable", Float.valueOf(1.0f)); 85 | // assertTrue(output == 1.0f); 86 | // } 87 | // 88 | // 89 | // /** 90 | // * Gets double environment variable. 91 | // * 92 | // * @throws Exception the exception 93 | // */ 94 | // @Test 95 | // public void getDoubleEnvironmentVariable() throws Exception{ 96 | // Map map = new HashMap<>(); 97 | // map.put("getDoubleEnvironmentVariable", "2.0"); 98 | // setEnv(map); 99 | // Double output = EnvironmentVariablesUtil.getDoubleEnvironmentVariable("getDoubleEnvironmentVariable", Double.valueOf(1.0f)); 100 | // assertTrue(output == 2.0); 101 | // } 102 | // 103 | // 104 | // /** 105 | // * Gets double null environment variable. 106 | // * 107 | // * @throws Exception the exception 108 | // */ 109 | // @Test 110 | // public void getDoubleNullEnvironmentVariable() throws Exception{ 111 | // Map map = new HashMap<>(); 112 | // map.put("getDoubleEnvironmentVariable", null); 113 | // setEnv(map); 114 | // Double output = EnvironmentVariablesUtil.getDoubleEnvironmentVariable("getDoubleEnvironmentVariable", Double.valueOf(1.0f)); 115 | // assertTrue(output == 1.0); 116 | // } 117 | // 118 | // /** 119 | // * Gets boolean environment variable. 120 | // * 121 | // * @throws Exception the exception 122 | // */ 123 | // @Test 124 | // public void getBooleanEnvironmentVariable() throws Exception{ 125 | // Map map = new HashMap<>(); 126 | // map.put("getBooleanEnvironmentVariable", "false"); 127 | // setEnv(map); 128 | // Boolean output = EnvironmentVariablesUtil.getBooleanEnvironmentVariable("getBooleanEnvironmentVariable", Boolean.valueOf("true")); 129 | // assertTrue(!output); 130 | // } 131 | // 132 | // 133 | // /** 134 | // * Gets boolean null environment variable. 135 | // * 136 | // * @throws Exception the exception 137 | // */ 138 | // @Test 139 | // public void getBooleanNullEnvironmentVariable() throws Exception{ 140 | // Map map = new HashMap<>(); 141 | // map.put("getBooleanEnvironmentVariable", null); 142 | // setEnv(map); 143 | // Boolean output = EnvironmentVariablesUtil.getBooleanEnvironmentVariable("getBooleanEnvironmentVariable", Boolean.valueOf("true")); 144 | // assertTrue(output); 145 | // } 146 | // 147 | // /** 148 | // * Gets string environment variable. 149 | // * 150 | // * @throws Exception the exception 151 | // */ 152 | // @Test 153 | // public void getStringEnvironmentVariable() throws Exception{ 154 | // Map map = new HashMap<>(); 155 | // map.put("getStringEnvironmentVariable", "temp"); 156 | // setEnv(map); 157 | // String output = EnvironmentVariablesUtil.getStringEnvironmentVariable("getStringEnvironmentVariable", "test"); 158 | // assertTrue(output.equals("temp")); 159 | // } 160 | // 161 | // 162 | // /** 163 | // * Gets string null environment variable. 164 | // * 165 | // * @throws Exception the exception 166 | // */ 167 | // @Test 168 | // public void getStringNullEnvironmentVariable() throws Exception{ 169 | // Map map = new HashMap<>(); 170 | // map.put("getStringEnvironmentVariable", null); 171 | // setEnv(map); 172 | // String output = EnvironmentVariablesUtil.getStringEnvironmentVariable("getStringEnvironmentVariable", "test"); 173 | // assertTrue(output.equals("test")); 174 | // } 175 | // 176 | // 177 | // /** 178 | // * Set ENV variables. 179 | // * @param newenv newenv 180 | // * @throws Exception Exception 181 | // */ 182 | // private static void setEnv(Map newenv) throws Exception { 183 | // try { 184 | // Class processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment"); 185 | // Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment"); 186 | // theEnvironmentField.setAccessible(true); 187 | // Map env = (Map) theEnvironmentField.get(null); 188 | // env.putAll(newenv); 189 | // Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); 190 | // theCaseInsensitiveEnvironmentField.setAccessible(true); 191 | // Map cienv = (Map) theCaseInsensitiveEnvironmentField.get(null); 192 | // cienv.putAll(newenv); 193 | // } catch (NoSuchFieldException e) { 194 | // Class[] classes = Collections.class.getDeclaredClasses(); 195 | // Map env = System.getenv(); 196 | // for(Class cl : classes) { 197 | // if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { 198 | // Field field = cl.getDeclaredField("m"); 199 | // field.setAccessible(true); 200 | // Object obj = field.get(env); 201 | // Map map = (Map) obj; 202 | // map.clear(); 203 | // map.putAll(newenv); 204 | // } 205 | // } 206 | // } 207 | // } 208 | //} -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateCallbackHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule; 19 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; 20 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback; 21 | import org.junit.Test; 22 | import org.mockito.Mockito; 23 | 24 | import javax.security.auth.callback.Callback; 25 | import javax.security.auth.callback.UnsupportedCallbackException; 26 | import javax.security.auth.login.AppConfigurationEntry; 27 | import java.io.IOException; 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | import static org.junit.Assert.*; 34 | import static org.mockito.Mockito.*; 35 | 36 | public class OAuthAuthenticateCallbackHandlerTest { 37 | 38 | @Test(expected = NullPointerException.class) 39 | public void constructor_NullOAuthServiceParameter_ThrowsArgumentNullException() { 40 | // act 41 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 42 | null 43 | ); 44 | } 45 | 46 | @Test() 47 | public void constructor_ValidParameters_ReturnsInitializedObject() { 48 | // arrange 49 | OAuthService oauthService = new OAuthServiceImpl(); 50 | 51 | 52 | // act 53 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 54 | oauthService 55 | ); 56 | 57 | // assert 58 | assertNotNull(callbackHandler); 59 | } 60 | 61 | @Test() 62 | public void getOauthService_ReturnsOAuthService() { 63 | // arrange 64 | OAuthService oauthService = new OAuthServiceImpl(); 65 | 66 | 67 | // act 68 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 69 | oauthService 70 | ); 71 | 72 | // assert 73 | assertNotNull(callbackHandler.getOauthService()); 74 | } 75 | 76 | @Test(expected = NullPointerException.class) 77 | public void configure_NullConfigsParameter_ThrowsNullPointerException() { 78 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 79 | 80 | // arrange 81 | OAuthService oauthService = new OAuthServiceImpl(); 82 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 83 | oauthService 84 | ); 85 | 86 | // act 87 | callbackHandler.configure(null, null, null); 88 | } 89 | 90 | @Test(expected = NullPointerException.class) 91 | public void configure_NullSaslMechanismParameter_ThrowsNullPointerException() { 92 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 93 | 94 | // arrange 95 | OAuthService oauthService = new OAuthServiceImpl(); 96 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 97 | oauthService 98 | ); 99 | 100 | Map configs = new HashMap<>(); 101 | 102 | // act 103 | callbackHandler.configure(configs, null, null); 104 | } 105 | 106 | @Test(expected = NullPointerException.class) 107 | public void configure_NullJaasConfigEntriesParameter_ThrowsNullPointerException() { 108 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 109 | 110 | // arrange 111 | OAuthService oauthService = new OAuthServiceImpl(); 112 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 113 | oauthService 114 | ); 115 | 116 | Map configs = new HashMap<>(); 117 | String saslMechanism = "Test"; 118 | 119 | // act 120 | callbackHandler.configure(configs, saslMechanism, null); 121 | } 122 | 123 | @Test() 124 | public void configure_ValidParameters_CallbackHandlerConfigured() { 125 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 126 | 127 | // arrange 128 | OAuthService oauthService = new OAuthServiceImpl(); 129 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 130 | oauthService 131 | ); 132 | 133 | Map configs = new HashMap<>(); 134 | String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM; 135 | List jaasConfigEntries = new ArrayList(); 136 | Map options = new HashMap<>(); 137 | 138 | jaasConfigEntries.add(new AppConfigurationEntry("Test", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)); 139 | 140 | // act 141 | callbackHandler.configure(configs, saslMechanism, jaasConfigEntries); 142 | 143 | // assert 144 | assertTrue(callbackHandler.isConfigured()); 145 | } 146 | 147 | @Test() 148 | public void configure_InvalidSaslMechanismParameter_CallbackHandlerIsNotConfigured() { 149 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 150 | 151 | // arrange 152 | OAuthService oauthService = new OAuthServiceImpl(); 153 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 154 | oauthService 155 | ); 156 | 157 | Map configs = new HashMap<>(); 158 | String saslMechanism = OAuthBearerLoginModule.OAUTHBEARER_MECHANISM;; 159 | List jaasConfigEntries = new ArrayList(); 160 | Map options = new HashMap<>(); 161 | 162 | // act 163 | callbackHandler.configure(configs, saslMechanism, jaasConfigEntries); 164 | 165 | // assert 166 | assertFalse(callbackHandler.isConfigured()); 167 | } 168 | 169 | @Test() 170 | public void configure_InvalidJaasConfigEntriesParameter_CallbackHandlerIsNotConfigured() { 171 | //configure(Map configs, String saslMechanism, List jaasConfigEntries 172 | 173 | // arrange 174 | OAuthService oauthService = new OAuthServiceImpl(); 175 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 176 | oauthService 177 | ); 178 | 179 | Map configs = new HashMap<>(); 180 | String saslMechanism = "Test"; 181 | List jaasConfigEntries = new ArrayList(); 182 | Map options = new HashMap<>(); 183 | 184 | jaasConfigEntries.add(new AppConfigurationEntry("Test", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)); 185 | 186 | // act 187 | callbackHandler.configure(configs, saslMechanism, jaasConfigEntries); 188 | 189 | // assert 190 | assertFalse(callbackHandler.isConfigured()); 191 | } 192 | 193 | @Test(expected = IllegalStateException.class) 194 | public void handle_NotConfigured_ThrowsIllegalStateException() throws IOException, UnsupportedCallbackException { 195 | // arrange 196 | OAuthService oauthService = new OAuthServiceImpl(); 197 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 198 | oauthService, 199 | false, 200 | false 201 | ); 202 | 203 | Map configs = new HashMap<>(); 204 | String saslMechanism = "Test"; 205 | List jaasConfigEntries = new ArrayList(); 206 | Map options = new HashMap<>(); 207 | 208 | jaasConfigEntries.add(new AppConfigurationEntry( 209 | "Test", 210 | AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, 211 | options)); 212 | 213 | Callback[] callbacks = new Callback[1]; 214 | callbacks[0] = new OAuthBearerTokenCallback(); 215 | 216 | // act 217 | callbackHandler.handle(callbacks); 218 | } 219 | 220 | @Test() 221 | public void handle_Configured_ProcessCallbacks() throws IOException, UnsupportedCallbackException { 222 | // arrange 223 | OAuthService oauthService = new OAuthServiceImpl(); 224 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 225 | oauthService, 226 | true, 227 | false 228 | ); 229 | 230 | TestOAuthAuthenticateCallbackHandler callbackHandlerSpy = Mockito.spy(callbackHandler); 231 | doCallRealMethod().when(callbackHandlerSpy).handle(any()); 232 | doCallRealMethod().when(callbackHandlerSpy).isConfigured(); 233 | 234 | Map configs = new HashMap<>(); 235 | List jaasConfigEntries = new ArrayList(); 236 | Map options = new HashMap<>(); 237 | 238 | jaasConfigEntries.add(new AppConfigurationEntry( 239 | "Test", 240 | AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, 241 | options)); 242 | 243 | Callback[] callbacks = new Callback[1]; 244 | callbacks[0] = new OAuthBearerTokenCallback(); 245 | 246 | // act 247 | callbackHandlerSpy.handle(callbacks); 248 | 249 | // assert 250 | verify(callbackHandlerSpy, times(1)).handleCallback(any()); 251 | } 252 | 253 | @Test(expected = IOException.class) 254 | public void handle_HandleCallbackThrowsException_ThrowsIOException() throws IOException, UnsupportedCallbackException { 255 | // arrange 256 | OAuthService oauthService = new OAuthServiceImpl(); 257 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 258 | oauthService, 259 | true, 260 | true 261 | ); 262 | 263 | TestOAuthAuthenticateCallbackHandler callbackHandlerSpy = Mockito.spy(callbackHandler); 264 | doCallRealMethod().when(callbackHandlerSpy).handle(any()); 265 | doCallRealMethod().when(callbackHandlerSpy).isConfigured(); 266 | 267 | List jaasConfigEntries = new ArrayList(); 268 | Map options = new HashMap<>(); 269 | 270 | jaasConfigEntries.add(new AppConfigurationEntry( 271 | "Test", 272 | AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, 273 | options)); 274 | 275 | OAuthBearerTokenCallback oauthBearerTokenCallback = new OAuthBearerTokenCallback(); 276 | 277 | Callback[] callbacks = new Callback[1]; 278 | callbacks[0] = oauthBearerTokenCallback; 279 | 280 | // act 281 | callbackHandlerSpy.handle(callbacks); 282 | 283 | // assert 284 | verify(callbackHandlerSpy, times(1)).handleCallback(any()); 285 | } 286 | 287 | // 288 | @Test(expected = UnsupportedCallbackException.class) 289 | public void handle_HandleCallbackThrowsException_ThrowsUnsupportedCallbackException() throws IOException, UnsupportedCallbackException { 290 | // arrange 291 | OAuthService oauthService = new OAuthServiceImpl(); 292 | TestOAuthAuthenticateCallbackHandler callbackHandler = new TestOAuthAuthenticateCallbackHandler( 293 | oauthService, 294 | true, 295 | true 296 | ); 297 | 298 | TestOAuthAuthenticateCallbackHandler callbackHandlerSpy = Mockito.spy(callbackHandler); 299 | doCallRealMethod().when(callbackHandlerSpy).handle(any()); 300 | doCallRealMethod().when(callbackHandlerSpy).isConfigured(); 301 | 302 | List jaasConfigEntries = new ArrayList(); 303 | Map options = new HashMap<>(); 304 | 305 | jaasConfigEntries.add(new AppConfigurationEntry( 306 | "Test", 307 | AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, 308 | options)); 309 | 310 | OAuthBearerValidatorCallback oauthBearerValidatorCallback = new OAuthBearerValidatorCallback("Test"); 311 | 312 | Callback[] callbacks = new Callback[1]; 313 | callbacks[0] = oauthBearerValidatorCallback; 314 | 315 | // act 316 | callbackHandlerSpy.handle(callbacks); 317 | 318 | // assert 319 | verify(callbackHandlerSpy, times(1)).handleCallback(any()); 320 | } 321 | 322 | private class TestOAuthAuthenticateCallbackHandler extends OAuthAuthenticateCallbackHandler { 323 | 324 | private boolean overrideIsConfigured; 325 | private boolean isConfigured = true; 326 | private boolean handleCallBackThrow = false; 327 | 328 | public TestOAuthAuthenticateCallbackHandler(OAuthService oauthService) { 329 | super(oauthService, OAuthBearerTokenCallback.class); 330 | } 331 | 332 | public TestOAuthAuthenticateCallbackHandler(OAuthService oauthService, boolean isConfigured, boolean handleCallBackThrow) { 333 | super(oauthService, OAuthBearerTokenCallback.class); 334 | this.overrideIsConfigured = isConfigured; 335 | this.isConfigured = isConfigured; 336 | this.handleCallBackThrow = handleCallBackThrow; 337 | } 338 | 339 | @Override 340 | protected void handleCallback(OAuthBearerTokenCallback oauthBearerTokenCallback) throws IOException { 341 | if (handleCallBackThrow) { 342 | throw new IOException("Error"); 343 | } 344 | } 345 | 346 | @Override 347 | protected boolean isConfigured() { 348 | if (this.overrideIsConfigured) { 349 | return this.isConfigured; 350 | } 351 | 352 | return super.isConfigured(); 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateLoginCallbackHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerTokenCallback; 19 | import org.junit.Test; 20 | import org.mockito.Mockito; 21 | 22 | import java.io.IOException; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | /** 29 | * The type O auth authenticate login callback handler test. 30 | */ 31 | public class OAuthAuthenticateLoginCallbackHandlerTest { 32 | 33 | /** 34 | * Handle callback successful token. 35 | * 36 | * @throws IOException the io exception 37 | */ 38 | @Test 39 | public void handleCallback_SuccessfulToken() throws IOException { 40 | OAuthAuthenticateLoginCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateLoginCallbackHandler()); 41 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 42 | Map response = new HashMap<>(); 43 | response.put("active", true); 44 | response.put("jti", ""); 45 | response.put("iat", 1); 46 | response.put("exp", 1); 47 | 48 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt(response, "test"); 49 | Mockito.doReturn(oauthServiceImplSpy).when(loginCallbackHandler).getOauthService(); 50 | Mockito.doReturn(jwt).when(oauthServiceImplSpy).requestAccessToken(); 51 | OAuthBearerTokenCallback oauthBearerTokenCallback = new OAuthBearerTokenCallback(); 52 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 53 | 54 | assertEquals(jwt, oauthBearerTokenCallback.token()); 55 | } 56 | 57 | /** 58 | * Handle callback token not null. 59 | * 60 | * @throws IOException the io exception 61 | */ 62 | @Test(expected = IllegalArgumentException.class) 63 | public void handleCallback_TokenParamNotNull() throws IOException { 64 | OAuthAuthenticateLoginCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateLoginCallbackHandler()); 65 | 66 | Map response = new HashMap<>(); 67 | response.put("active", true); 68 | response.put("jti", ""); 69 | response.put("iat", 1); 70 | response.put("exp", 1); 71 | 72 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt(response, "test"); 73 | 74 | OAuthBearerTokenCallback oauthBearerTokenCallback = new OAuthBearerTokenCallback(); 75 | oauthBearerTokenCallback.token(jwt); 76 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 77 | 78 | } 79 | 80 | @Test(expected = IllegalArgumentException.class) 81 | public void handleCallback_TokenResultNull() throws IOException { 82 | OAuthAuthenticateLoginCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateLoginCallbackHandler()); 83 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 84 | Map response = new HashMap<>(); 85 | response.put("active", true); 86 | response.put("jti", ""); 87 | response.put("iat", 1); 88 | response.put("exp", 1); 89 | 90 | Mockito.doReturn(oauthServiceImplSpy).when(loginCallbackHandler).getOauthService(); 91 | Mockito.doReturn(null).when(oauthServiceImplSpy).requestAccessToken(); 92 | OAuthBearerTokenCallback oauthBearerTokenCallback = new OAuthBearerTokenCallback(); 93 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 94 | 95 | } 96 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthAuthenticateValidatorCallbackHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.security.oauthbearer.OAuthBearerValidatorCallback; 19 | import org.junit.Test; 20 | import org.mockito.Mockito; 21 | 22 | import java.io.IOException; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | /** 29 | * The type O auth authenticate validator callback handler test. 30 | */ 31 | public class OAuthAuthenticateValidatorCallbackHandlerTest { 32 | /** 33 | * Handle callback successful token. 34 | * 35 | * @throws IOException the io exception 36 | */ 37 | @Test 38 | public void handleCallback_SuccessfulToken() throws IOException { 39 | OAuthAuthenticateValidatorCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateValidatorCallbackHandler()); 40 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 41 | Map response = new HashMap<>(); 42 | response.put("active", true); 43 | response.put("jti", ""); 44 | response.put("iat", 1); 45 | response.put("exp", 1); 46 | 47 | OAuthBearerTokenJwt jwt = new OAuthBearerTokenJwt(response, "test"); 48 | Mockito.doReturn(oauthServiceImplSpy).when(loginCallbackHandler).getOauthService(); 49 | Mockito.doReturn(jwt).when(oauthServiceImplSpy).validateAccessToken("test"); 50 | OAuthBearerValidatorCallback oauthBearerTokenCallback = new OAuthBearerValidatorCallback("test"); 51 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 52 | 53 | assertEquals(jwt, oauthBearerTokenCallback.token()); 54 | } 55 | 56 | /** 57 | * Handle callback null token. 58 | * 59 | * @throws IOException the io exception 60 | */ 61 | @Test(expected = NullPointerException.class) 62 | public void handleCallback_NullToken() throws IOException { 63 | OAuthAuthenticateValidatorCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateValidatorCallbackHandler()); 64 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 65 | Map response = new HashMap<>(); 66 | response.put("active", true); 67 | response.put("jti", ""); 68 | response.put("iat", 1); 69 | response.put("exp", 1); 70 | 71 | Mockito.doReturn(oauthServiceImplSpy).when(loginCallbackHandler).getOauthService(); 72 | Mockito.doReturn(null).when(oauthServiceImplSpy).validateAccessToken("test"); 73 | OAuthBearerValidatorCallback oauthBearerTokenCallback = new OAuthBearerValidatorCallback("test"); 74 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 75 | 76 | } 77 | 78 | /** 79 | * Handle callback null param token. 80 | * 81 | * @throws IOException the io exception 82 | */ 83 | @Test(expected = IllegalArgumentException.class) 84 | public void handleCallback_NullParamToken() throws IOException { 85 | OAuthAuthenticateValidatorCallbackHandler loginCallbackHandler = Mockito.spy(new OAuthAuthenticateValidatorCallbackHandler()); 86 | 87 | OAuthBearerValidatorCallback oauthBearerTokenCallback = Mockito.spy(new OAuthBearerValidatorCallback("test")); 88 | Mockito.doReturn(null).when(oauthBearerTokenCallback).tokenValue(); 89 | loginCallbackHandler.handleCallback(oauthBearerTokenCallback); 90 | 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthBearerTokenJwtTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.junit.Test; 19 | 20 | public class OAuthBearerTokenJwtTests { 21 | @Test 22 | public void constructor_NullAccessToken_ThrowsException() { 23 | /* // arrange 24 | String accessToken = ""; 25 | long lifeTimeMs = 100; 26 | long startTimeMs = 100; 27 | String principalName = ""; 28 | 29 | // act 30 | OAuthBearerTokenJwt oauthBearerTokenJwt = new OAuthBearerTokenJwt(); 31 | 32 | // assert 33 | assertNotNull(oauthBearerTokenJwt);*/ 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthConfigurationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.apache.kafka.common.protocol.types.Field; 19 | import org.junit.Test; 20 | 21 | import java.util.Map; 22 | import java.util.TreeMap; 23 | 24 | import static org.junit.Assert.*; 25 | 26 | public class OAuthConfigurationTests { 27 | 28 | @Test 29 | public void constructor_NoParameters_ReturnsDefaultObjectCreated() { 30 | // act 31 | OAuthConfiguration oauthConfiguration = new OAuthConfiguration(); 32 | 33 | // assert 34 | assertNotNull(oauthConfiguration); 35 | } 36 | 37 | @Test 38 | public void testSetConfigurationUsingJaasConfiguratioFile(){ 39 | Map jaasConfigurationEntries = new TreeMap<>(); 40 | 41 | String serverBaseUri = "http://localhost:8080/auth/realms/master1/protocol/openid-connect"; 42 | String serverEndpointPath = "/token1"; 43 | String serverIntrospectionEndpointPath = "/token1/introspect"; 44 | String serverClientId = "test-consumer-1"; 45 | String serverClientSecret = "7b3c23e1-8909-489e-bf4a-64a84abb197e"; 46 | String serverGrantType = "client_credentials"; 47 | String serverScopes = "test-1"; 48 | String serverAcceptUnsecureServer = "false"; 49 | 50 | jaasConfigurationEntries.put("oauth.server.base.uri", serverBaseUri); 51 | jaasConfigurationEntries.put("oauth.server.token.endpoint.path", serverEndpointPath); 52 | jaasConfigurationEntries.put("oauth.server.introspection.endpoint.path", serverIntrospectionEndpointPath); 53 | jaasConfigurationEntries.put("oauth.server.client.id", serverClientId); 54 | jaasConfigurationEntries.put("oauth.server.client.secret", serverClientSecret); 55 | jaasConfigurationEntries.put("oauth.server.grant.type", serverGrantType); 56 | jaasConfigurationEntries.put("oauth.server.scopes", serverScopes); 57 | jaasConfigurationEntries.put("oauth.server.accept.unsecure.server", serverAcceptUnsecureServer); 58 | 59 | OAuthConfiguration oauthConfiguration = new OAuthConfiguration(); 60 | oauthConfiguration.setConfigurationFromJaasConfigEntries(jaasConfigurationEntries); 61 | 62 | assertTrue(oauthConfiguration.getBaseServerUri().equals(serverBaseUri)); 63 | assertTrue(oauthConfiguration.getTokenEndpointPath().equals(serverEndpointPath)); 64 | assertTrue(oauthConfiguration.getIntrospectionEndpointPath().equals(serverIntrospectionEndpointPath)); 65 | assertTrue(oauthConfiguration.getClientId().equals(serverClientId)); 66 | assertTrue(oauthConfiguration.getClientSecret().equals(serverClientSecret)); 67 | assertTrue(oauthConfiguration.getGrantType().equals(serverGrantType)); 68 | assertTrue(oauthConfiguration.getScopes().equals(serverScopes)); 69 | assertFalse(oauthConfiguration.getUnsecureServer()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthScopeTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.assertTrue; 21 | 22 | public class OAuthScopeTest { 23 | 24 | @Test 25 | public void testGettersSetters() { 26 | OAuthScope oAuthScope = new OAuthScope(); 27 | oAuthScope.setResourceType("test"); 28 | oAuthScope.setResourceName("testName"); 29 | oAuthScope.setOperation("TEST"); 30 | 31 | assertTrue(oAuthScope.getOperation().equals("TEST")); 32 | assertTrue(oAuthScope.getResourceName().equals("testName")); 33 | assertTrue(oAuthScope.getResourceType().equals("test")); 34 | } 35 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/OAuthServiceImplTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.mockito.Mockito; 21 | import org.mockito.junit.MockitoJUnitRunner; 22 | 23 | import java.io.IOException; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import static org.junit.Assert.assertNotNull; 28 | import static org.junit.Assert.assertNull; 29 | import static org.mockito.ArgumentMatchers.anyString; 30 | 31 | /** 32 | * The type O auth service impl tests. 33 | */ 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class OAuthServiceImplTests { 36 | 37 | //region Tests 38 | 39 | /** 40 | * Constructor initialized object created. 41 | */ 42 | @Test() 43 | public void constructor_InitializedObjectCreated() { 44 | // act 45 | OAuthServiceImpl oauthServiceImpl = new OAuthServiceImpl(); 46 | 47 | // assert 48 | assertNotNull(oauthServiceImpl.getOAuthConfiguration()); 49 | } 50 | 51 | /** 52 | * Request access token http call does not return response returns null. 53 | * 54 | * @throws IOException the io exception 55 | */ 56 | @Test() 57 | public void requestAccessToken_HttpCallDoesNotReturnResponse_ReturnsNull() throws IOException { 58 | // arrange 59 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 60 | Mockito.doReturn(null).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 61 | 62 | // act 63 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.requestAccessToken(); 64 | 65 | // assert 66 | assertNull(oAuthBearerTokenJwt); 67 | } 68 | 69 | /** 70 | * Request access token http call does return response returns null. 71 | * 72 | * @throws IOException the io exception 73 | */ 74 | @Test() 75 | public void requestAccessToken_HttpCallDoesReturnResponse_ReturnsNull() throws IOException { 76 | // arrange 77 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 78 | Mockito.doReturn(null).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 79 | 80 | // act 81 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.requestAccessToken(); 82 | 83 | // assert 84 | assertNull(oAuthBearerTokenJwt); 85 | } 86 | 87 | /** 88 | * Request access token http call does return response returns valid object. 89 | * 90 | * @throws IOException the io exception 91 | */ 92 | @Test() 93 | public void requestAccessToken_HttpCallDoesReturnResponse_ReturnsValidObject() throws IOException { 94 | // arrange 95 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 96 | Map response = new HashMap<>(); 97 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN, "test-client-id"); 98 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN_EXPIRES_IN, 12); 99 | Mockito.doReturn(response).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 100 | 101 | 102 | 103 | // act 104 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.requestAccessToken(); 105 | 106 | // assert 107 | assertNotNull(oAuthBearerTokenJwt); 108 | } 109 | 110 | /** 111 | * Validate access token http call returns response returns null. 112 | * 113 | * @throws IOException the io exception 114 | */ 115 | @Test() 116 | public void validateAccessToken_HttpCallReturnsResponse_ReturnsNull() throws IOException { 117 | // arrange 118 | Map response = new HashMap<>(); 119 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN, "test-client-id"); 120 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN_EXPIRES_IN, 12); 121 | response.put("active", false); 122 | 123 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 124 | Mockito.doReturn(response).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 125 | 126 | // act 127 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.validateAccessToken("test"); 128 | 129 | // assert 130 | assertNull(oAuthBearerTokenJwt); 131 | } 132 | 133 | /** 134 | * Validate access token http call does not return response returns null. 135 | * 136 | * @throws IOException the io exception 137 | */ 138 | @Test() 139 | public void validateAccessToken_HttpCallDoesNotReturnResponse_ReturnsNull() throws IOException { 140 | // arrange 141 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 142 | Mockito.doReturn(null).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 143 | 144 | // act 145 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.validateAccessToken("test"); 146 | 147 | // assert 148 | assertNull(oAuthBearerTokenJwt); 149 | } 150 | 151 | /** 152 | * Validate access token http call returns response returns valid object. 153 | * 154 | * @throws IOException the io exception 155 | */ 156 | @Test() 157 | public void validateAccessToken_HttpCallReturnsResponse_ReturnsValidObject() throws IOException { 158 | // arrange 159 | Map response = new HashMap<>(); 160 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN, "test-client-id"); 161 | response.put(OAuthServiceImpl.OAUTH_ACCESS_TOKEN_EXPIRES_IN, 12); 162 | response.put("active", true); 163 | response.put("jti", ""); 164 | response.put("iat", 1); 165 | response.put("exp", 1); 166 | 167 | OAuthServiceImpl oauthServiceImplSpy = Mockito.spy(new OAuthServiceImpl()); 168 | Mockito.doReturn(response).when(oauthServiceImplSpy).doHttpCall(anyString(), anyString(), anyString()); 169 | 170 | // act 171 | OAuthBearerTokenJwt oAuthBearerTokenJwt = oauthServiceImplSpy.validateAccessToken("test"); 172 | 173 | // assert 174 | assertNotNull(oAuthBearerTokenJwt); 175 | } 176 | 177 | //endregion 178 | } 179 | -------------------------------------------------------------------------------- /kafka-oauth/src/test/java/com/bfm/kafka/security/oauthbearer/UtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2019 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka.security.oauthbearer; 17 | 18 | import org.junit.Test; 19 | 20 | import static org.junit.Assert.*; 21 | 22 | /** 23 | * The type Utils test. 24 | */ 25 | public class UtilsTest { 26 | 27 | /** 28 | * Is uri valid. 29 | */ 30 | @Test 31 | public void isURIValid() { 32 | assertTrue(Utils.isURIValid("www.google.com")); 33 | assertFalse(Utils.isURIValid(null)); 34 | } 35 | 36 | /** 37 | * Is null or empty. 38 | */ 39 | @Test 40 | public void isNullOrEmpty() { 41 | assertTrue(Utils.isNullOrEmpty(null)); 42 | assertTrue(Utils.isNullOrEmpty("")); 43 | assertFalse(Utils.isNullOrEmpty("test")); 44 | } 45 | 46 | /** 47 | * Create basic authorization header. 48 | */ 49 | @Test 50 | public void createBasicAuthorizationHeader() { 51 | assertNotNull(Utils.createBasicAuthorizationHeader("clientId", "clientSecret")); 52 | } 53 | 54 | /** 55 | * Create bearer header. 56 | */ 57 | @Test 58 | public void createBearerHeader() { 59 | assertEquals(Utils.createBearerHeader("token"), "Bearer token"); 60 | } 61 | } -------------------------------------------------------------------------------- /kafka-oauth/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /kafka-oauth/src/test/resources/oauth-configuration.properties: -------------------------------------------------------------------------------- 1 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 2 | oauth.server.token.endpoint.path=/token 3 | oauth.server.introspection.endpoint.path=/token/introspect 4 | oauth.server.client.id=test-consumer 5 | oauth.server.client.secret=7b3c23ef-8909-489e-bf4a-64a84abb197e 6 | oauth.server.grant.type=client_credentials 7 | oauth.server.scopes=test 8 | oauth.server.accept.unsecure.server=true -------------------------------------------------------------------------------- /kafka-producer-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ 17 | 18 | 19 | # Project 20 | target/ 21 | /null/ 22 | /.checkstyle 23 | .eclipse-pmd 24 | .settings/org.springframework.ide.eclipse.prefs 25 | git.properties 26 | /logs/ 27 | /${sys:myserver.writable.root.dir}/ 28 | -------------------------------------------------------------------------------- /kafka-producer-example/README.md: -------------------------------------------------------------------------------- 1 | Copyright © 2020 BlackRock Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | #Producer Example using the Kafka OAuth library 16 | 17 | #### Add libkafka.oauthbearer dependency in your `pom.xml` file 18 | 19 | 20 | brs 21 | libkafka.oauthbearer 22 | 1.0.0 23 | 24 | 25 | 26 | #### Add the OAuth configuration to your Producer 27 | // OAuth Settings 28 | // - sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required; 29 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"); 30 | 31 | // - security.protocol=SASL_PLAINTEXT 32 | props.put("security.protocol", "SASL_PLAINTEXT"); 33 | 34 | // - sasl.mechanism=OAUTHBEARER 35 | props.put("sasl.mechanism", "OAUTHBEARER"); 36 | 37 | // - sasl.login.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler 38 | props.put("sasl.login.callback.handler.class", "com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler"); 39 | 40 | 41 | #### Build the JAR 42 | - If you are running inside a IDE you can skip this step 43 | 44 | #### Config Files 45 | 46 | - Create a properties file for your Producer OAuth client {oauth-configuration.properties}. 47 | - There is one already in your resource folder! 48 | - The file should contain the following properties: 49 | - NOTE: you will need to change oauth.server.client.secret to be your client secret 50 | 51 | 52 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 53 | oauth.server.token.endpoint.path=/token 54 | oauth.server.introspection.endpoint.path=/token/introspect 55 | oauth.server.client.id=test-producer 56 | oauth.server.client.secret=4ce54cfb-f359-4400-bd8e-810a1af10f71 57 | oauth.server.grant.type=client_credentials 58 | oauth.server.scopes=test 59 | oauth.server.accept.unsecure.server=true 60 | # oauth.server.accept.unsecure.server - this propertie is for SSL configuration, if you are using HTTP or a self-signed CERT set this true 61 | 62 | 63 | 64 | 65 | #### Configure your ENV Variables 66 | 67 | set KAFKA_OAUTH_SERVER_PROP_FILE={PATH_TO_PROJECT}\kafka-producer-example\src\main\resources\oauth-configuration.properties 68 | 69 | 70 | #### Run the Producer 71 | java -jar kafka-producer-example/target/kakfa-oauth-producer-example-0.0.1-SNAPSHOT-jar-with-dependencies.jar 72 | 73 | OR 74 | 75 | Run the main Java class from your IDE 76 | -------------------------------------------------------------------------------- /kafka-producer-example/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | brs 7 | kafka-security 8 | 1.0-SNAPSHOT 9 | ../pom.xml 10 | 11 | 12 | com.bfm.kafka 13 | kakfa-oauth-producer-example 14 | 0.0.1-SNAPSHOT 15 | jar 16 | kakfa-oauth-producer-example 17 | http://maven.apache.org 18 | 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | org.apache.kafka 26 | kafka_2.12 27 | 2.2.0 28 | 29 | 30 | 31 | com.fasterxml.jackson.core 32 | jackson-databind 33 | 2.5.0 34 | 35 | 36 | 37 | org.apache.logging.log4j 38 | log4j-api 39 | 2.7 40 | 41 | 42 | 43 | org.apache.logging.log4j 44 | log4j-core 45 | 2.7 46 | 47 | 48 | 49 | org.apache.logging.log4j 50 | log4j-slf4j-impl 51 | 2.7 52 | 53 | 54 | 55 | brs 56 | libkafka.oauthbearer 57 | 1.0.0 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 67 | 8 68 | 8 69 | 70 | 3.8.1 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-assembly-plugin 75 | 76 | 77 | package 78 | 79 | single 80 | 81 | 82 | 83 | 84 | 85 | com.bfm.kafka.ProducerApp 86 | 87 | 88 | 89 | 90 | jar-with-dependencies 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /kafka-producer-example/src/main/java/com/bfm/kafka/CustomObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import java.io.Serializable; 19 | 20 | public class CustomObject implements Serializable{ 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | private String id; 25 | 26 | private String name; 27 | 28 | public String getId() { 29 | return id; 30 | } 31 | 32 | public void setId(String id) { 33 | this.id = id; 34 | } 35 | 36 | public String getName() { 37 | return name; 38 | } 39 | 40 | public void setName(String name) { 41 | this.name = name; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /kafka-producer-example/src/main/java/com/bfm/kafka/IKafkaConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | public interface IKafkaConstants { 19 | String KAFKA_BROKERS = "localhost:9092"; 20 | 21 | Integer MESSAGE_COUNT=1000; 22 | 23 | String CLIENT_ID="test-producer"; 24 | 25 | String TOPIC_NAME="test"; 26 | } 27 | -------------------------------------------------------------------------------- /kafka-producer-example/src/main/java/com/bfm/kafka/ProducerApp.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import org.apache.kafka.clients.producer.Producer; 19 | import org.apache.kafka.clients.producer.ProducerRecord; 20 | import org.apache.kafka.clients.producer.RecordMetadata; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.util.concurrent.ExecutionException; 25 | 26 | public class ProducerApp { 27 | 28 | private final static Logger log = LoggerFactory.getLogger(Producer.class); 29 | 30 | public static void main(String[] args) { 31 | runProducer(); 32 | } 33 | 34 | static void runProducer() { 35 | 36 | Producer producer = ProducerCreator.createProducer(); 37 | 38 | for (int index = 0; index < IKafkaConstants.MESSAGE_COUNT; index++) { 39 | final ProducerRecord record = new ProducerRecord(IKafkaConstants.TOPIC_NAME, 40 | "This is record " + index); 41 | try { 42 | RecordMetadata metadata = producer.send(record).get(); 43 | System.out.println("Record sent with key " + index + " to partition " + metadata.partition() 44 | + " with offset " + metadata.offset()); 45 | } catch (ExecutionException e) { 46 | System.out.println("Error in sending record"); 47 | System.out.println(e); 48 | } catch (InterruptedException e) { 49 | System.out.println("Error in sending record"); 50 | System.out.println(e); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kafka-producer-example/src/main/java/com/bfm/kafka/ProducerCreator.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2020 BlackRock Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package com.bfm.kafka; 17 | 18 | import org.apache.kafka.clients.producer.KafkaProducer; 19 | import org.apache.kafka.clients.producer.Producer; 20 | import org.apache.kafka.clients.producer.ProducerConfig; 21 | import org.apache.kafka.common.serialization.LongSerializer; 22 | import org.apache.kafka.common.serialization.StringSerializer; 23 | 24 | import java.util.Properties; 25 | 26 | public class ProducerCreator { 27 | 28 | public static Producer createProducer() { 29 | Properties props = new Properties(); 30 | props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, IKafkaConstants.KAFKA_BROKERS); 31 | props.put(ProducerConfig.CLIENT_ID_CONFIG, IKafkaConstants.CLIENT_ID); 32 | props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName()); 33 | props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); 34 | 35 | // OAuth Settings 36 | // - sasl.jaas.config=org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required; 37 | props.put("sasl.jaas.config", "org.apache.kafka.common.security.oauthbearer.OAuthBearerLoginModule required;"); 38 | 39 | // - security.protocol=SASL_PLAINTEXT 40 | props.put("security.protocol", "SASL_PLAINTEXT"); 41 | 42 | // - sasl.mechanism=OAUTHBEARER 43 | props.put("sasl.mechanism", "OAUTHBEARER"); 44 | 45 | // - sasl.login.callback.handler.class=com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler 46 | props.put("sasl.login.callback.handler.class", "com.bfm.kafka.security.oauthbearer.OAuthAuthenticateLoginCallbackHandler"); 47 | 48 | return new KafkaProducer<>(props); 49 | } 50 | } -------------------------------------------------------------------------------- /kafka-producer-example/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /kafka-producer-example/src/main/resources/oauth-configuration.properties: -------------------------------------------------------------------------------- 1 | oauth.server.base.uri=http://localhost:8080/auth/realms/master/protocol/openid-connect 2 | oauth.server.token.endpoint.path=/token 3 | oauth.server.introspection.endpoint.path=/token/introspect 4 | oauth.server.client.id=test-producer 5 | oauth.server.client.secret=ea443769-1126-49b6-b672-456d26f1af65 6 | oauth.server.grant.type=client_credentials 7 | oauth.server.scopes=test 8 | oauth.server.accept.unsecure.server=true -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | brs 6 | kafka-security 7 | 1.0-SNAPSHOT 8 | pom 9 | 10 | 11 | kafka-oauth 12 | kafka-consumer-example 13 | kafka-producer-example 14 | 15 | 16 | --------------------------------------------------------------------------------