├── .gitattributes ├── .gitignore ├── package.json ├── Makefile ├── SECURITY.md ├── CHANGELOG.md ├── .github └── workflows │ └── ci.yaml ├── .markdownlint.yaml ├── README.md ├── LICENSE └── style-guide.md /.gitattributes: -------------------------------------------------------------------------------- 1 | Makefile linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "markdown-toc": "^1.2.0", 4 | "markdownlint-cli2": "^0.18.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHONY: deps 2 | deps: 3 | npm install 4 | 5 | markdownlint: deps 6 | npx markdownlint-cli2 style-guide.md --config=.markdownlint.yaml 7 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Please refer to the [OPA Security Policy](https://openpolicyagent.org/security) 4 | for details on how to report security issues, our disclosure policy, and how to 5 | receive notifications about security issues. 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | Note that since this is just a document of recommendations, semantic versioning is not used here. 5 | Versions are only used to keep track of changes from one version to another. 6 | 7 | ## [0.2.0] 8 | ### Added 9 | - Recommendation on using explicit imports for future keywords 10 | 11 | ### Changed 12 | - Updated all examples to make use of the new future keywords 13 | 14 | ## [0.1.0] 15 | ### Added 16 | - Initial release -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: 18 13 | - run: npm install 14 | - run: make markdownlint 15 | - run: git diff 16 | - run: | 17 | if [ -z "$(git status --porcelain)" ]; then 18 | echo "No changes" 19 | else 20 | echo "Please fix the markdownlint errors" 21 | exit 1 22 | fi 23 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | 3 | # MD013/line-length 4 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md013.md 5 | MD013: 6 | line_length: 120 7 | heading_line_length: 80 8 | code_blocks: false 9 | tables: false 10 | headings: true 11 | 12 | # MD024/no-duplicate-heading 13 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md024.md 14 | MD024: 15 | siblings_only: true 16 | 17 | # MD026/no-trailing-punctuation 18 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md026.md 19 | MD026: 20 | # Punctuation characters 21 | punctuation: ".,;:,;:!" 22 | 23 | # MD031/blanks-around-fences 24 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md031.md 25 | MD031: false 26 | 27 | # MD033/no-inline-html 28 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md033.md 29 | MD033: 30 | allowed_elements: 31 | - br 32 | - details 33 | - img 34 | - strong 35 | - summary 36 | 37 | # MD036/no-emphasis-as-heading 38 | # https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md036.md 39 | MD036: false 40 | 41 | MD004: 42 | style: dash 43 | 44 | MD010: false 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > **Note**: This repo has recently been donated to the OPA GitHub organization 3 | > and we are in the process of integrating it into the OPA website. There may be 4 | > some references to the repo's previous location while we do that. 5 | 6 | # Rego Style Guide 7 | 8 | This repository contains source code for the 9 | [Rego Style Guide](https://openpolicyagent.org/docs/rego-style-guide). 10 | 11 | The purpose of this style guide is to provide a collection of recommendations 12 | and best practices for authoring 13 | [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/). 14 | 15 | This repo is informed by OPA maintainers 16 | and some of the most experienced members of the community, 17 | we hope to share lessons learnt from authoring and reviewing hundreds of 18 | thousands of lines of Rego over the years. 19 | 20 | > [!NOTE] 21 | > You might also be interested in checking out 22 | > [Regal](https://www.openpolicyagent.org/projects/regal), 23 | > the linter and language server for Rego, which automates many style guide 24 | > recommendations. 25 | 26 | If you are interested in suggesting changes to the guide's content, 27 | please feel free to raise an issue or PR on this repo. The document 28 | containing the guide's content can be found in `style-guide.md`. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 [yyyy] [name of copyright owner] 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 | -------------------------------------------------------------------------------- /style-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: Style Guide 3 | --- 4 | 5 | # Rego Style Guide 6 | 7 | 8 | 9 | The purpose of this style guide is to provide a collection of recommendations and best practices for authoring 10 | [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/). 11 | From the maintainers of [Open Policy Agent](https://www.openpolicyagent.org) (OPA), 12 | and some of the most experienced members of the community, 13 | we hope to share lessons learnt from authoring and reviewing hundreds of thousands of lines of Rego over the years. 14 | 15 | With new features, language constructs, and other improvements continuously finding their way into OPA, we aim to keep 16 | this style guide a reflection of what we consider current best practices. Make sure to check back every once in a while, 17 | and see the changelog for updates since your last visit. 18 | 19 | ## Regal 20 | 21 | Inspired by this style guide, [Regal](/projects/regal) is a new linter for Rego that allows you 22 | to enforce many of the recommendations in this guide, as well as identifying issues, bugs and potential problems in your 23 | Rego policies. If you enjoy this style guide, make sure to check it out! 24 | 25 | ## General Advice 26 | 27 | ### Optimize for readability, not performance 28 | 29 | Rego is a declarative language, which in the best case means you express **what** you want rather than **how** it 30 | should be retrieved. When authoring policy, do not try to be "smart" about assumed performance characteristics or 31 | optimizations. That's what OPA should worry about! 32 | 33 | Optimize for **readability** and **obviousness**. Optimize for performance _only_ if you've identified performance 34 | issues in your policy, and even if you do — making your policy more compact or "clever" almost never helps at addressing 35 | the problem at hand. 36 | 37 | #### Related Resources 38 | 39 | - [Policy Performance](https://www.openpolicyagent.org/docs/latest/policy-performance/) 40 | 41 | ### Use `opa fmt` 42 | 43 | The `opa fmt` tool ensures consistent formatting across teams and projects. While certainly not 44 | [perfect](https://github.com/open-policy-agent/opa/issues/4508) (yet!), unified formatting is a big win, and saves a 45 | lot of time in code reviews arguing over details around style. 46 | 47 | A good idea could be to run `opa fmt --write` on save, which can be configured in most editors. If you want to enforce 48 | `opa fmt` formatting as part of your build pipeline, use `opa fmt --fail`. 49 | 50 | In order to not flood this guide with data, formatting conventions covered by `opa fmt` will not be included here. 51 | 52 | **Tip**: `opa fmt` uses tabs for indentation. By default, GitHub uses 8 spaces to display tabs, which is arguably a bit 53 | much. You can change this preference for your account in 54 | [github.com/settings/appearance](https://github.com/settings/appearance), 55 | or provide an [.editorconfig](https://editorconfig.org/) file in your policy repository, which will be used by GitHub 56 | (and other tools) to properly display your Rego files: 57 | 58 | ```ini 59 | [*.rego] 60 | end_of_line = lf 61 | insert_final_newline = true 62 | charset = utf-8 63 | indent_style = tab 64 | indent_size = 4 65 | ``` 66 | 67 | Sadly, there doesn't seem to be a way to enforce this for code blocks displayed in markdown (`.md`) files. 68 | 69 | :::tip 70 | You can lint for this recommendation using the [`opa-fmt`](/projects/regal/rules/style/opa-fmt) 71 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 72 | ::: 73 | 74 | ### Use strict mode 75 | 76 | [Strict mode](https://www.openpolicyagent.org/docs/latest/strict/) provides extra checks for common mistakes like 77 | redundant imports, or unused variables. Include an `opa check --strict path/to/polices` step as part of your build 78 | pipeline. 79 | 80 | ### Use metadata annotations 81 | 82 | Favor [metadata annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata) over regular comments. 83 | 84 | Metadata annotations allow external tools and editors to parse their contents, potentially leveraging them for 85 | something useful, like in-line explanations, generated docs, etc. 86 | 87 | Annotations are also a good way to de-duplicate information such as documentation links, contact emails 88 | and error codes where explanations are returned as part of the result. 89 | 90 | **Avoid** 91 | ```rego 92 | # Example package with documentation 93 | package example 94 | 95 | import future.keywords.contains 96 | import future.keywords.if 97 | 98 | # E123: Deny non admin users. 99 | # Only admin users are allowed to access these resources, see https://docs.example.com/policy/rule/E123 100 | deny contains { 101 | "code": 401, 102 | "message": "Unauthorized due to policy rule (E123, https://docs.example.com/policy/rule/E123)", 103 | } if { 104 | input.admin == false 105 | } 106 | ``` 107 | 108 | **Prefer** 109 | ```rego 110 | # METADATA 111 | # title: Example 112 | # description: Example package with documentation 113 | package example 114 | 115 | import future.keywords.contains 116 | import future.keywords.if 117 | 118 | # METADATA 119 | # title: Deny non admin users 120 | # description: Only admin users are allowed to access these resources 121 | # related_resources: 122 | # - https://docs.example.com/policy/rule/E123 123 | # custom: 124 | # code: 401 125 | # error_id: E123 126 | deny contains { 127 | "code": metadata.custom.code, 128 | "message": sprintf("Unauthorized due to policy rule (%s, %s)", [ 129 | metadata.custom.error_id, 130 | concat(",", [ref | ref := metadata.related_resources[_].ref]), 131 | ]), 132 | } if { 133 | input.admin == false 134 | 135 | metadata := rego.metadata.rule() 136 | } 137 | ``` 138 | 139 | **Notes / Exceptions** 140 | 141 | Use regular comments inside of rule bodies, or for packages and rules you consider "internal". 142 | 143 | #### Related Resources 144 | 145 | - [Annotations](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata) 146 | 147 | ### Get to know the built-in functions 148 | 149 | With more than 150 [built-in functions](https://www.openpolicyagent.org/docs/latest/policy-reference/#built-in-functions) 150 | tailor-made for policy evaluation, there's a good chance that some of them can help you accomplish your goal. 151 | 152 | ### Consider using JSON schemas for type checking 153 | 154 | As you author Rego policy, providing JSON schemas for your `input` (and possibly `data`) enables strict 155 | [type checking](https://www.openpolicyagent.org/docs/latest/schemas/), letting you avoid simple — but common — mistakes, 156 | like typos, or referencing nested attributes in the wrong location. This extra level of verification improves both the 157 | developer experience as well as the quality of your policies. 158 | 159 | ## Style 160 | 161 | ### Prefer snake_case for rule names and variables 162 | 163 | The built-in functions use `snake_case` for naming — follow that convention for your own rules, functions, and variables. 164 | 165 | **Avoid** 166 | ```rego 167 | userIsAdmin if "admin" in input.user.roles 168 | ``` 169 | 170 | **Prefer** 171 | ```rego 172 | user_is_admin if "admin" in input.user.roles 173 | ``` 174 | 175 | **Notes / Exceptions** 176 | 177 | In many cases, you might not control the format of the `input` data — if the domain of a policy (e.g. Envoy) 178 | mandates a different style, making an exception might seem reasonable. Adapting policy format after `input` is however 179 | prone to inconsistencies, as you'll likely end up mixing different styles in the same policy (due to imports of common 180 | code, etc). 181 | 182 | :::tip 183 | You can lint for this recommendation using the [`prefer-snake-case`](/projects/regal/rules/style/prefer-snake-case) 184 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 185 | ::: 186 | 187 | ### Optionally, use leading underscore for rules intended for internal use 188 | 189 | While OPA doesn't have "private" rules or functions, a pretty common convention that we've seen in the community is to 190 | use a leading underscore for rules and functions that are intended to be internal to the package that they are in: 191 | 192 | ```rego 193 | developers contains user if { 194 | some user in input.users 195 | _is_developer(user) 196 | } 197 | 198 | _is_developer(user) if { 199 | # some conditions 200 | } 201 | 202 | _is_developer(user) if { 203 | # some other conditions 204 | } 205 | ``` 206 | 207 | While an `is_developer` function may seem like a good candidate for reuse, it could easily be the case that this should 208 | be considered to what **this** package considers a developer, and not necessarily a universal truth. Using a leading 209 | underscore to denote this is a good way to communicate this intent, but there are also other ways to do this, like 210 | agreed upon naming conventions, or using custom metadata annotation attributes. 211 | 212 | One benefit of sticking to the leading underscore convention is that tools like [Regal](/projects/regal), 213 | and the language server for Rego that it provides, may use this information to provide better suggestions, like not 214 | adding references to these rules and functions from other packages. 215 | 216 | ### Keep line length `<=` 120 characters 217 | 218 | Long lines are tedious to read. Keep line length at 120 characters or below. 219 | 220 | **Avoid** 221 | ```rego 222 | frontend_admin_users := [username | some user in input.users; "frontend" in user.domains; "admin" in user.roles; username := user.username] 223 | ``` 224 | 225 | **Prefer** 226 | ```rego 227 | frontend_admin_users := [username | 228 | some user in input.users 229 | "frontend" in user.domains 230 | "admin" in user.roles 231 | username := user.username 232 | ] 233 | ``` 234 | 235 | :::tip 236 | You can lint for this recommendation using the [`line-length`](/projects/regal/rules/style/line-length) 237 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 238 | ::: 239 | 240 | ## Rules 241 | 242 | ### Use helper rules and functions 243 | 244 | Helper rules makes policies more readable, and for repeated conditions more performant as well. If your rule contains 245 | more than a few simple expressions, consider splitting it into multiple rules with good names. 246 | 247 | **Avoid** 248 | ```rego 249 | allow if { 250 | "developer" in input.user.roles 251 | input.request.method in {"GET", "HEAD"} 252 | startswith(input.request.path, "/docs") 253 | } 254 | 255 | allow if { 256 | "developer" in input.user.roles 257 | input.request.method in {"GET", "HEAD"} 258 | startswith(input.request.path, "/api") 259 | } 260 | ``` 261 | 262 | **Prefer** 263 | ```rego 264 | allow if { 265 | is_developer 266 | read_request 267 | startswith(input.request.path, "/docs") 268 | } 269 | 270 | allow if { 271 | is_developer 272 | read_request 273 | startswith(input.request.path, "/api") 274 | } 275 | 276 | read_request if input.request.method in {"GET", "HEAD"} 277 | 278 | is_developer if "developer" in input.user.roles 279 | ``` 280 | 281 | Additionally, helper rules and functions may be kept in (and imported from) separate modules, allowing you to build a 282 | logical — and reusable! — structure for your policy files. 283 | 284 | ### Use negation to handle undefined 285 | 286 | When encountering undefined references inside of rules, evaluation of the rule halts and the rule _itself_ evaluates to 287 | undefined, unless of course, a `default` value has been provided. While saying `allow is undefined` or `allow is false` 288 | if encountering undefined in a rule is likely desirable, this doesn't hold true when working with "inverted" rules - 289 | i.e. rules like `deny` (as opposed to `allow`). Saying `deny is undefined` or `deny is false` if undefined is 290 | encountered, essentially means that any occurrence of undefined (such as when attributes are missing in the input 291 | document) would lead to the `deny` rule not getting enforced. This is particularly common writing partial rules (i.e. 292 | rules that build [sets](https://www.openpolicyagent.org/docs/latest/policy-language/#generating-sets) or 293 | [objects](https://www.openpolicyagent.org/docs/latest/policy-language/#generating-objects)). 294 | 295 | Consider for example this simple rule: 296 | 297 | **Avoid** 298 | ```rego 299 | authorized := count(deny) == 0 300 | 301 | deny contains "User is anonymous" if input.user_id == "anonymous" 302 | ``` 303 | 304 | At first glance, it might seem obvious that evaluating the rule should add a violation to the set of messages if the 305 | `user_id` provided in `input` is equal to "anonymous". But what happens if there is no `user_id` provided _at all_? 306 | Evaluation will stop when encountering undefined, and the comparison will never be invoked, leading to **nothing** 307 | being added to the `deny` set — the rule allows someone without a `user_id`. We could of course add another 308 | rule, checking only for its presence: 309 | 310 | ```rego 311 | deny contains "User ID missing from input" if not input.user_id 312 | ``` 313 | 314 | This is nice in that we'll get an even more granular message returned to the caller, but quickly becomes tedious when 315 | working with a large set of input data. To deal with this, a helper rule using _negation_ may be used. 316 | 317 | **Prefer** 318 | ```rego 319 | authorized := count(deny) == 0 320 | 321 | deny contains "User is anonymous" if not authenticated_user 322 | 323 | authenticated_user if input.user_id != "anonymous" 324 | ``` 325 | 326 | In the above case, the `authenticated_user` rule will fail **both** in the the undefined case, and if defined 327 | but equal to "anonymous". Since we negate the result of the helper rule in the `deny` rule, we'll have both 328 | cases covered. 329 | 330 | #### Related Resources 331 | 332 | - [OPA AWS CloudFormation Hook Tutorial](https://www.openpolicyagent.org/docs/latest/aws-cloudformation-hooks/) 333 | 334 | ### Consider partial helper rules over comprehensions in rule bodies 335 | 336 | While comprehensions inside of rule bodies allows for compact rules, these are often harder to debug, and can't easily 337 | be reused by other rules. Partial rules may be referenced by any other rule, and more importantly, by you! 338 | Having many smaller, composable rules, is often key to quickly identifying where things fail, as each rule may be 339 | queried individually. 340 | 341 | **Avoid** 342 | ```rego 343 | allow if { 344 | input.request.method in {"GET", "HEAD"} 345 | input.request.path[0] == "credit_reports" 346 | input.user.name in {username | 347 | # These should not count as MFA 348 | insecure_methods := {"email"} 349 | 350 | some user in data.users 351 | mfa_methods := {method | some method in user.authentication.methods} - insecure_methods 352 | 353 | count(mfa_methods) > 1 354 | username := user.name 355 | } 356 | } 357 | ``` 358 | 359 | **Prefer** 360 | ```rego 361 | allow if { 362 | input.request.method in {"GET", "HEAD"} 363 | input.request.path[0] == "credit_reports" 364 | input.user.name in mfa_authenticated_users 365 | } 366 | 367 | mfa_authenticated_users contains username if { 368 | # These should not count as MFA 369 | insecure_methods := {"email"} 370 | 371 | some user in data.users 372 | mfa_methods := {method | some method in user.authentication.methods} - insecure_methods 373 | 374 | count(mfa_methods) > 1 375 | username := user.name 376 | } 377 | ``` 378 | 379 | **Notes / Exceptions** 380 | 381 | Does not apply if ordering is of importance, or duplicate values should be allowed. For those cases, use array 382 | comprehensions. 383 | 384 | ### Avoid prefixing rules and functions with `get_` or `list_` 385 | 386 | Since Rego evaluation is generally free of side effects, any rule or function is essentially a "getter". Adding a 387 | `get_` prefix to a rule or function (like `get_resources`) thus adds little of value compared to just naming it 388 | `resources`. Additionally, the type and return value of the rule should serve to tell whether a rule might return a 389 | single value (i.e. a complete rule) or a collection (a partial rule). 390 | 391 | **Avoid** 392 | ```rego 393 | get_first_name(user) := split(user.name, " ")[0] 394 | 395 | # Partial rule, so a set of users is to be expected 396 | list_developers contains user if { 397 | some user in data.application.users 398 | user.type == "developer" 399 | } 400 | ``` 401 | 402 | **Prefer** 403 | ```rego 404 | # "get" is implied 405 | first_name(user) := split(user.name, " ")[0] 406 | 407 | # Partial rule, so a set of users is to be expected 408 | developers contains user if { 409 | some user in data.application.users 410 | user.type == "developer" 411 | } 412 | ``` 413 | 414 | **Notes / Exceptions** 415 | 416 | Using `is_`, or `has_` for boolean helper functions, like `is_admin(user)` may be easier to comprehend than 417 | `admin(user)`. 418 | 419 | :::tip 420 | You can lint for this recommendation using the [`avoid-get-and-list-prefix`](/projects/regal/rules/style/avoid-get-and-list-prefix) 421 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 422 | ::: 423 | 424 | ### Prefer unconditional assignment in rule head over rule body 425 | 426 | Rules that return values unconditionally should place the assignment directly in the rule head, as doing so 427 | in the rule body adds unnecessary noise. 428 | 429 | **Avoid** 430 | ```rego 431 | full_name := name { 432 | name := concat(", ", [input.first_name, input.last_name]) 433 | } 434 | 435 | divide_by_ten(x) := y { 436 | y := x / 10 437 | } 438 | ``` 439 | 440 | **Prefer** 441 | ```rego 442 | full_name := concat(", ", [input.first_name, input.last_name]) 443 | 444 | divide_by_ten(x) := x / 10 445 | ``` 446 | 447 | :::tip 448 | You can lint for this recommendation using the [`unconditional-assignment`](/projects/regal/rules/style/unconditional-assignment) 449 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 450 | ::: 451 | 452 | ## Variables and Data Types 453 | 454 | ### Use `in` to check for membership 455 | 456 | Using `in` for membership checks clearly communicates intent, and is less prone to errors. This is especially true when 457 | checking if something is _not_ part of a collection. 458 | 459 | **Avoid** 460 | ```rego 461 | # "Old" way of checking for membership - iteration + comparison 462 | allow { 463 | "admin" == input.user.roles[_] 464 | } 465 | ``` 466 | 467 | **Prefer** 468 | ```rego 469 | allow if "admin" in input.user.roles 470 | ``` 471 | 472 | **Avoid** 473 | ```rego 474 | deny contains "Only admin allowed" if not user_is_admin 475 | 476 | user_is_admin if { 477 | "admin" == input.user.roles[_] 478 | } 479 | ``` 480 | 481 | **Prefer** 482 | ```rego 483 | deny contains "Only admin allowed" if not "admin" in input.user.roles 484 | ``` 485 | 486 | :::tip 487 | You can lint for this recommendation using the [`use-in-operator`](/projects/regal/rules/idiomatic/use-in-operator) 488 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 489 | ::: 490 | 491 | ### Prefer `some .. in` for iteration 492 | 493 | Using the `some` .. `in` construct for iteration removes ambiguity around iteration vs. membership checks, and is 494 | generally more pleasant to read. 495 | 496 | **Avoid** 497 | ```rego 498 | my_rule if { 499 | # Are we iterating users over a partial "other_rule" here, 500 | # or checking if the set contains a user defined elsewhere? 501 | other_rule[user] 502 | } 503 | ``` 504 | 505 | While this could be alleviated by declaring `some user` before the iteration, we can't take that consideration for 506 | granted when reading code from someone else. 507 | 508 | **Avoid** 509 | ```rego 510 | # Iterating over array 511 | internal_hosts contains hostname if { 512 | host := data.network.hosts[_] 513 | host.internal == true 514 | hostname := host.name 515 | } 516 | 517 | # Iterating over object 518 | public_endpoints contains endpoint if { 519 | some endpoint 520 | attributes := endpoints[endpoint] 521 | attributes.public 522 | } 523 | ``` 524 | 525 | **Prefer** 526 | ```rego 527 | internal_hosts contains hostname if { 528 | some host in data.network.hosts 529 | host.internal == true 530 | hostname := host.name 531 | } 532 | 533 | # Iterating over object 534 | public_endpoints contains endpoint if { 535 | some endpoint, attributes in endpoints 536 | attributes.public 537 | } 538 | ``` 539 | 540 | **Notes / Exceptions** 541 | 542 | Using the "old" style of iteration may still be preferable when iterating over deeply nested structures. 543 | 544 | ```rego 545 | # Building a list of all hostnames from a deeply nested structure 546 | 547 | all_hostnames := [hostname | hostname := data.regions[_].networks[_].servers[_].hostname] 548 | 549 | # ⬆️ is likely preferable over ⬇️ 550 | 551 | all_hostnames := [hostname | 552 | some region in data.regions 553 | some network in region 554 | some server in network 555 | hostname := server.hostname 556 | ] 557 | ``` 558 | 559 | :::tip 560 | You can lint for this recommendation using the [`prefer-some-in-iteration`](/projects/regal/rules/style/prefer-some-in-iteration) 561 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 562 | ::: 563 | 564 | ### Use `every` to express FOR ALL 565 | 566 | The `every` keyword makes it trivial to describe "for all" type expressions, which previously required the use of 567 | helper rules, or comparing counts of items in the original collection against a filtered one produced by a 568 | comprehension. 569 | 570 | **Avoid** 571 | ```rego 572 | # Negate result of _any_ match 573 | allow if not any_old_registry 574 | 575 | any_old_registry if { 576 | some container in input.request.object.spec.containers 577 | startswith(container.image, "old.docker.registry/") 578 | } 579 | ``` 580 | 581 | **Prefer** 582 | ```rego 583 | allow if { 584 | every container in input.request.object.spec.containers { 585 | not startswith(container.image, "old.docker.registry/") 586 | } 587 | } 588 | ``` 589 | 590 | **Avoid** 591 | ```rego 592 | words := ["always", "arbitrary", "air", "brand", "asphalt"] 593 | 594 | all_starts_with_a if { 595 | starts_with_a := [word | 596 | some word in words 597 | startswith(word, "a") 598 | ] 599 | count(starts_with_a) == count(words) 600 | } 601 | ``` 602 | 603 | **Prefer** 604 | ```rego 605 | words := ["always", "arbitrary", "air", "brand", "asphalt"] 606 | 607 | all_starts_with_a if { 608 | every word in words { 609 | startswith(word, "a") 610 | } 611 | } 612 | ``` 613 | 614 | **Notes / Exceptions** 615 | 616 | Older versions of OPA used the `all` built-in function to check that all elements of an array had the value `true`. 617 | This function has been deprecated for a long time, and will eventually be removed. 618 | 619 | ### Don't use unification operator for assignment or comparison 620 | 621 | The [unification](https://www.openpolicyagent.org/docs/latest/policy-language/#unification-) operator (`=`) allows you 622 | to combine assignment and comparison. While this is useful in a few specific cases (see "Notes / Exceptions" below), 623 | using the assignment operator (`:=`) for assignment, and the comparison operator (`==`) for comparison, is almost always 624 | preferable. Separating assignment from comparison clearly demonstrates intent, and removes the ambiguity around scope 625 | associated with unification. 626 | 627 | **Avoid** 628 | ```rego 629 | # Top level assignment using unification operator 630 | roles = input.user.roles 631 | 632 | allow if { 633 | # Unification operator - used for assignment to `username` variable or for 634 | # comparing to a `username` variable or rule defined elsewhere? Who knows. 635 | username = input.user.name 636 | 637 | # ... 638 | } 639 | 640 | allow if { 641 | # Unification operator used for comparison 642 | input.request.method = "GET" 643 | } 644 | 645 | allow if { 646 | some user 647 | input.request.path = ["users", user] 648 | input.request.user == user 649 | } 650 | ``` 651 | 652 | **Prefer** 653 | ```rego 654 | # Top level assignment using assignment operator 655 | roles := input.user.roles 656 | 657 | allow if { 658 | # Assignment operator used for assignment - no ambiguity around 659 | # intent, or variable scope 660 | username := input.user.name 661 | 662 | # ... do something with username 663 | } 664 | 665 | allow if { 666 | # Comparison operator used for comparison 667 | input.request.method == "GET" 668 | } 669 | 670 | allow if { 671 | input.request.path == ["users", input.request.user] 672 | } 673 | ``` 674 | 675 | **Notes / Exceptions** 676 | 677 | Unification was used extensively in older versions of OPA, and following that, in the policy examples provided in 678 | the OPA documentation, blogs, and elsewhere. With the assignment and comparison operators now available for use in 679 | any context, there are generally few reasons to use the unification operator in modern Rego. 680 | 681 | One notable exception is when matching for example, the path of a request (as presented in array form), 682 | where you'll want to do both comparison and assignment to variables from the path components: 683 | 684 | ```rego 685 | # Using unification - compact but clear 686 | router { 687 | some user_id, podcast_id 688 | ["users", user_id, "podcasts", podcast_id] = input.request.path 689 | 690 | # .. do something with user_id, podcast_id 691 | } 692 | 693 | # Using comparison + assignment - arguably messier 694 | router { 695 | input.request_path[0] == "users" 696 | input.request_path[2] == "podcasts" 697 | 698 | user_id := input.request_path[1] 699 | podcast_id := input.request_path[3] 700 | 701 | # .. do something with user_id, podcast_id 702 | } 703 | ``` 704 | 705 | #### Related Resources 706 | 707 | - [Strict-mode to phase-out the "single =" operator](https://github.com/open-policy-agent/opa/issues/4688) 708 | - [OPA fmt 2.0](https://github.com/open-policy-agent/opa/issues/4508) 709 | 710 | :::tip 711 | You can lint for this recommendation using the [`use-assignment-operator`](/projects/regal/rules/style/use-assignment-operator) 712 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 713 | ::: 714 | 715 | ### Don't use undeclared variables 716 | 717 | Using undeclared variables (i.e. not declared using `some` or `:=`) makes it harder to understand what's going on 718 | in a rule, and introduces ambiguities around scope. 719 | 720 | **Avoid** 721 | ```rego 722 | messages contains message if { 723 | message := input.topics[topic].body 724 | } 725 | ``` 726 | 727 | **Prefer** 728 | ```rego 729 | messages contains message if { 730 | some topic 731 | message := input.topics[topic].body 732 | } 733 | 734 | # Alternatively 735 | messages contains message if { 736 | some topic in input.topics 737 | message := topic.body 738 | } 739 | 740 | # or 741 | 742 | messages contains message if { 743 | message := input.topics[_].body 744 | } 745 | ``` 746 | 747 | :::tip 748 | You can lint for this recommendation using the [`use-some-for-output-vars`](/projects/regal/rules/idiomatic/use-some-for-output-vars) 749 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 750 | ::: 751 | 752 | ### Prefer sets over arrays (where applicable) 753 | 754 | For any _unordered_ sequence of _unique_ values, prefer to use 755 | [sets](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets) over 756 | [arrays](https://www.openpolicyagent.org/docs/latest/policy-reference/#arrays). 757 | 758 | This is almost always the case for common policy data like **roles** and **permissions**. 759 | For any applicable sequence of values, sets have the following benefits over arrays: 760 | 761 | - Clearly communicate uniqueness and non-ordered characteristics 762 | - Performance: set lookups are O(1) while array lookups are O(n) 763 | - Powerful [set operations](https://www.openpolicyagent.org/docs/latest/policy-reference/#sets-2) available 764 | 765 | **Avoid** 766 | ```rego 767 | required_roles := ["accountant", "reports-writer"] 768 | provided_roles := [role | some role in input.user.roles] 769 | 770 | allow if { 771 | every required_role in required_roles { 772 | required_role in provided_roles 773 | } 774 | } 775 | ``` 776 | 777 | **Prefer** 778 | ```rego 779 | required_roles := {"accountant", "reports-writer"} 780 | provided_roles := {role | some role in input.user.roles} 781 | 782 | allow if { 783 | every required_role in required_roles { 784 | required_role in provided_roles 785 | } 786 | } 787 | ``` 788 | 789 | **Prefer** 790 | ```rego 791 | # Alternatively, use set intersection 792 | allow if { 793 | required_roles & provided_roles == required_roles 794 | } 795 | ``` 796 | 797 | #### Related Resources 798 | 799 | - [Five things you didn't know about OPA](https://blog.styra.com/blog/five-things-you-didnt-know-about-opa). 800 | 801 | ## Functions 802 | 803 | ### Prefer using arguments over `input`, `data` or rule references 804 | 805 | What separates functions from rules is that they accept _arguments_. While a function too may reference anything from 806 | `input`, `data` or other rules declared in a policy, these references create dependencies that aren't obvious simply by 807 | checking the function signature, and it makes it harder to reuse that function in other contexts. Additionally, 808 | functions that only depend on their arguments are easier to test standalone. 809 | 810 | **Avoid** 811 | ```rego 812 | # Depends on both `input` and `data` 813 | is_preferred_login_method(method) if { 814 | preferred_login_methods := {login_method | 815 | some login_method in data.authentication.all_login_methods 816 | login_method in input.user.login_methods 817 | } 818 | method in preferred_login_methods 819 | } 820 | ``` 821 | 822 | **Prefer** 823 | ```rego 824 | # Depends only on function arguments 825 | is_preferred_login_method(method, user, all_login_methods) if { 826 | preferred_login_methods := {login_method | 827 | some login_method in all_login_methods 828 | login_method in user.login_methods 829 | } 830 | method in preferred_login_methods 831 | } 832 | ``` 833 | 834 | ### Avoid using the last argument for the return value 835 | 836 | Older Rego policies sometimes contain an unusual way to declare where the return value of a function call should be 837 | stored — the last argument of the function. True to it's 838 | [Datalog](https://www.openpolicyagent.org/docs/latest/policy-language/#what-is-rego) roots, return values may be stored 839 | either using assignment (i.e. `:=`) or by appending a variable name to the argument list of a function. These two 840 | expressions are thus equivalent: 841 | 842 | **Avoid** 843 | ```rego 844 | first_a := i if { 845 | indexof("answer", "a", i) 846 | } 847 | ``` 848 | 849 | **Prefer** 850 | ```rego 851 | first_a := i if { 852 | i := indexof("answer", "a") 853 | } 854 | ``` 855 | 856 | While the first form is valid, it is almost guaranteed to confuse developers coming from the most common programming 857 | languages. Again, optimize for readability! 858 | 859 | :::tip 860 | You can lint for this recommendation using the [`function-arg-return`](/projects/regal/rules/style/function-arg-return) 861 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 862 | ::: 863 | 864 | ## Regex 865 | 866 | ### Use raw strings for regex patterns 867 | 868 | [Raw strings](https://www.openpolicyagent.org/docs/edge/policy-language/#strings) are interpreted literally, allowing 869 | you to avoid having to escape special characters like `\` in your regex patterns. 870 | 871 | **Avoid** 872 | ```rego 873 | all_digits if { 874 | regex.match("[\\d]+", "12345") 875 | } 876 | ``` 877 | 878 | **Prefer** 879 | ```rego 880 | all_digits if { 881 | regex.match(`[\d]+`, "12345") 882 | } 883 | ``` 884 | 885 | :::tip 886 | You can lint for this recommendation using the [`non-raw-regex-pattern`](/projects/regal/rules/idiomatic/non-raw-regex-pattern) 887 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 888 | ::: 889 | 890 | ## Packages 891 | 892 | ### Package name should match file location 893 | 894 | When naming packages, the package name should reflect the file location. This 895 | makes the package implementation easier to find when looking up from elsewhere 896 | in a project as well. 897 | 898 | When choosing to follow this recommendation, there are two options: 899 | 900 | - **Matching the directory and filename** 901 | - Pros: Reduced nesting for simple policies. 902 | - Cons: Large packages can become unwieldy in long files. 903 | - **Matching the directory only** 904 | - Pros: Large packages can be broken into many files. 905 | - Cons: Exception needed to co-locate test files (i.e. `package foo_test` 906 | should still be in `foo/`). 907 | 908 | Either is acceptable, just remember to use the same convention throughout 909 | your project. 910 | 911 | #### Matching the directory and filename 912 | 913 | **Avoid** 914 | ```rego 915 | # foo/bar.rego 916 | package bar.foo 917 | 918 | # ... 919 | ``` 920 | 921 | **Prefer** 922 | ```rego 923 | # foo/bar.rego 924 | package foo.bar 925 | 926 | # ... 927 | ``` 928 | 929 | #### Matching the directory only 930 | 931 | **Avoid** 932 | ```rego 933 | # foo/bar.rego 934 | package baz 935 | 936 | # ... 937 | ``` 938 | 939 | **Prefer** 940 | ```rego 941 | # foo/bar.rego 942 | package foo 943 | 944 | # ... 945 | ``` 946 | 947 | ## Imports 948 | 949 | ### Prefer importing packages over rules and functions 950 | 951 | Importing packages rather than specific rules and functions allows you to reference them by the package name, making it 952 | obvious where the rule or function was declared. Additionally, well-named packages help provide context to assertions. 953 | 954 | **Avoid** 955 | ```rego 956 | import data.user.is_admin 957 | 958 | allow if is_admin 959 | ``` 960 | 961 | **Prefer** 962 | ```rego 963 | import data.user 964 | 965 | allow if user.is_admin 966 | ``` 967 | 968 | :::tip 969 | You can lint for this recommendation using the [`prefer-package-imports`](/projects/regal/rules/imports/prefer-package-imports) 970 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 971 | ::: 972 | 973 | ### Avoid importing `input` 974 | 975 | While importing attributes from the global `input` variable might eliminate some levels of nesting, it makes the origin 976 | of the attribute(s) less apparent. Clearly differentiating `input` and `data` from values, functions, and rules 977 | defined inside of the same package helps in making things _obvious_, and few things beat obviousness! 978 | 979 | **Avoid** 980 | ```rego 981 | import input.request.context.user 982 | 983 | # ... many lines of code later 984 | 985 | fin_dept if { 986 | # where does "user" come from? 987 | contains(user.department, "finance") 988 | } 989 | ``` 990 | 991 | **Prefer** 992 | ```rego 993 | fin_dept if { 994 | contains(input.request.context.user.department, "finance") 995 | } 996 | ``` 997 | 998 | **Prefer** 999 | ```rego 1000 | fin_dept if { 1001 | # Alternatively, assign an intermediate variable close to where it's referenced 1002 | user := input.request.context.user 1003 | contains(user.department, "finance") 1004 | } 1005 | ``` 1006 | 1007 | **Notes / Exceptions** 1008 | 1009 | In some contexts, the source of data is obvious even when imported and/or renamed. A common practice is 1010 | to rename `input` in Terraform policies for example, either via `import` or a new top-level variable. 1011 | 1012 | ```rego 1013 | import input as tfplan 1014 | 1015 | violations contains message if { 1016 | # still obvious where "tfplan" comes from, perhaps even more so — this is generally acceptable 1017 | some change in tfplan.resource_changes 1018 | # ... 1019 | } 1020 | ``` 1021 | 1022 | :::tip 1023 | You can lint for this recommendation using the [`avoid-importing-input`](/projects/regal/rules/imports/avoid-importing-input) 1024 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 1025 | ::: 1026 | 1027 | ## Older Advice 1028 | 1029 | Advice placed here was valid at the time, but has since been replaced by new recommendations. Kept here for the sake 1030 | of completeness, and to provide context for older policies. 1031 | 1032 | ### Use explicit imports for future keywords 1033 | 1034 | **With the introduction of the `import rego.v1` construct in OPA v0.59.0, this is no longer needed** 1035 | 1036 | In order to evolve the Rego language without breaking existing policies, many new features require importing 1037 | ["future" keywords](https://www.openpolicyagent.org/docs/latest/policy-language/#future-keywords), like `contains`, 1038 | `every`, `if` and `in`. While it might seem convenient to use the "catch-all" form of `import future.keywords` to 1039 | import all of the future keywords, this construct risks breaking your policies when new keywords are introduced, and 1040 | their names happen to collide with names you've used for variables or rules. 1041 | 1042 | **Avoid** 1043 | ```rego 1044 | import future.keywords 1045 | 1046 | severe_violations contains violation if { 1047 | some violation in input.violations 1048 | violation.severity > 5 1049 | } 1050 | ``` 1051 | 1052 | **Prefer** 1053 | ```rego 1054 | import future.keywords.contains 1055 | import future.keywords.if 1056 | import future.keywords.in 1057 | 1058 | severe_violations contains violation if { 1059 | some violation in input.violations 1060 | violation.severity > 5 1061 | } 1062 | ``` 1063 | 1064 | **Tip**: Importing the `every` keyword implicitly imports `in` as well, as it is required by the `every` construct. 1065 | Leaving out the import of `in` when `every` is imported is considered okay. 1066 | 1067 | :::tip 1068 | You can lint for this recommendation using the [`implicit-future-keywords`](/projects/regal/rules/imports/implicit-future-keywords) 1069 | Regal rule. Get started with [Regal, the Rego linter](/projects/regal). 1070 | ::: 1071 | 1072 | --- 1073 | 1074 | ## Contributing 1075 | 1076 | This document is meant to reflect the style preferences and best practices as compiled by the OPA community. As such, 1077 | we welcome contributions from any of its members. Since most of the topics in a guide like this are likely subject to 1078 | discussion, please open an issue, and allow some time for people to comment, before opening a PR. 1079 | 1080 | If you'd like to add or remove items for your own company, team or project, forking this repo is highly encouraged! 1081 | --------------------------------------------------------------------------------