├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── Readme.md ├── builtins ├── builtins.go ├── check_permission.go ├── delete_relationships.go ├── lookup_resources.go ├── lookup_subjects.go ├── read_relationships.go └── write_relationships.go ├── demo ├── docker-compose.yaml ├── opa-config-demo.yaml ├── policy-demo.rego └── schema-and-data.yaml ├── doc ├── opa-config.yaml.example └── opa-spicedb-demo.gif ├── go.mod ├── go.sum ├── main.go └── plugins ├── plugins.go └── spicedb └── plugin.go /.gitignore: -------------------------------------------------------------------------------- 1 | opa-spicedb 2 | opa-config.yaml 3 | .DS_Store 4 | *~ 5 | _temp 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILDPLATFORM="linux/amd64" 2 | ARG BUILDERIMAGE="golang:1.22-bookworm" 3 | # Use distroless as minimal base image to package the manager binary 4 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 5 | ARG BASEIMAGE="gcr.io/distroless/static-debian12:nonroot" 6 | 7 | FROM --platform=$BUILDPLATFORM $BUILDERIMAGE AS builder 8 | 9 | ARG TARGETPLATFORM 10 | ARG TARGETOS 11 | ARG TARGETARCH 12 | ARG TARGETVARIANT="" 13 | ARG LDFLAGS 14 | ARG BUILDKIT_SBOM_SCAN_STAGE=true 15 | 16 | ENV GO111MODULE=on \ 17 | CGO_ENABLED=0 \ 18 | GOOS=${TARGETOS} \ 19 | GOARCH=${TARGETARCH} \ 20 | GOARM=${TARGETVARIANT} 21 | 22 | WORKDIR /go/src/github.com/umbrella-associates/opa-spicedb 23 | COPY . . 24 | 25 | #RUN go build -mod vendor -a -ldflags "${LDFLAGS}" -o manager 26 | RUN go build -ldflags "${LDFLAGS}" -o opa-spicedb 27 | 28 | FROM $BASEIMAGE 29 | 30 | WORKDIR / 31 | COPY --from=builder /go/src/github.com/umbrella-associates/opa-spicedb/opa-spicedb . 32 | USER 65532:65532 33 | ENTRYPOINT ["/opa-spicedb"] 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := opa-spicedb 2 | REPOSITORY := openpolicyagent/$(BIN) 3 | VERSION := 0.1-dev 4 | GO := /usr/lib/go-1.22/bin/go 5 | 6 | 7 | build: build-go 8 | build-go: 9 | $(GO) build -o $(BIN) . 10 | 11 | build-static: 12 | $(GO) build -ldflags="-linkmode external -extldflags=-static" -o $(BIN) . 13 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Open Policy Agent with support for Authzed SpiceDB 2 | --- 3 | 4 | This plugin adds support for querying and manipulating relations from [Authzed](https://authzed.com/) [SpiceDB](https://github.com/authzed/spicedb) via gRPC as custom builtin commands for [Open Policy Agent](https://www.openpolicyagent.org/). 5 | 6 | 7 |
8 | topaz model visualization 9 | 10 | 11 | ## Why use OPA? 12 | 13 | [OPA (Open Policy Agent)](https://www.openpolicyagent.org/) decouples policy from code in a highly-performant and elegant way, which makes it perfect for use as an external PDP (Policy Decision Point) for applictions in your stack, implementing a Policy-Based Access Control scheme (PBAC). 14 | 15 | ## Why use Authzed SpiceDB? 16 | 17 | [Authzed SpiceDB](https://authzed.com/spicedb) is an open source authorization system for Relationship-Based Access Control (ReBAC), originally inspired by [Google's Zanzibar paper](https://www.usenix.org/conference/atc19/presentation/pang) and one of the most advanced implementation of it. 18 | 19 | 20 | ## Policy 📃 + Relations 🧠 = 💪 fine-grained access control 21 | 22 | PBAC and ReBAC are both strong models for fine-grained access control, while OPA and SpiceDB are award winning solutions and the best-of-breed products for their respective categories. 23 | 24 | Combining PBAC and ReBAC results in a flexible and powerful authorizer that can effectively used to protect millions of objects. 25 |
26 | 27 | 28 | ## Supported methods and features 29 | 30 | - SpiceDB gRPC interface available in Rego 31 | - automatic schema-prefix removal 32 | 33 | Currently implemented methods: 34 | - check_permission 35 | - lookup_resources 36 | - lookup_subjects 37 | - read_relationships 38 | - write_relationships 39 | - delete_relationships 40 | 41 | 42 | ### Builtin rego functions for SpiceDB 43 | 44 | #### Check permission: 45 | 46 | ``` 47 | 48 | spicedb.check_permission("resourceType", "resourceId", "permission", "subjectType", "subjectId") 49 | 50 | ## result: 51 | { 52 | "lookedUpAt": "", 53 | "result": true 54 | } 55 | 56 | ``` 57 | 58 | #### Resource lookup 59 | 60 | ``` 61 | spicedb.lookup_resources("resourceType", "permission", "subjectType", "subjectId") 62 | 63 | ## result: 64 | { 65 | "lookedUpAt": "", 66 | "permission": "", 67 | "resourceObjectIds": [ 68 | "", 69 | "" 70 | ], 71 | "resourceObjectType": "", 72 | "result": true, 73 | "subjectId": "", 74 | "subjectType": "" 75 | } 76 | 77 | ``` 78 | 79 | #### Subject lookup 80 | 81 | ``` 82 | spicedb.lookup_subjects("", "", "", "") 83 | ## result: 84 | { 85 | "lookedUpAt": "", 86 | "permission": "", 87 | "resourceObjectId": "", 88 | "resourceObjectType": "", 89 | "result": true, 90 | "subjectIds": [ 91 | "", 92 | "" 93 | ], 94 | "subjectType": "" 95 | } 96 | 97 | ``` 98 | 99 | #### Write, touch and delete relationships in a single request 100 | 101 | ``` 102 | write_relations := [ 103 | {"resourceType": "", "resourceId": "", "relationship": "", "subjectType": "", "subjectId": ""}, 104 | ] 105 | 106 | touch_relations := [] 107 | delete_relations := [] 108 | 109 | spicedb.write_relationships(write_relations, touch_relations, delete_relations) 110 | 111 | ## result: 112 | { 113 | "result": true, 114 | "writtenAt": "" 115 | } 116 | 117 | ``` 118 | 119 | #### Perform read relationships request 120 | 121 | ``` 122 | 123 | spicedb.read_relationships("", "", "", "", "") 124 | 125 | ## result: 126 | { 127 | "lookedUpAt": "", 128 | "result": true, 129 | "relationships": [ 130 | { 131 | "relationship": "", 132 | "resourceId": "", 133 | "resourceType": "", 134 | "subjectId": "", 135 | "subjectType": "" 136 | } 137 | ] 138 | } 139 | 140 | 141 | ``` 142 | 143 | #### Perform delete relationships request 144 | 145 | ``` 146 | spicedb.delete_relationships("", "", "", "", "") 147 | 148 | ## result: 149 | { 150 | "deletedAt": "", 151 | "result": true 152 | } 153 | 154 | ``` 155 | 156 | # Build 🚀 157 | 158 | Make sure you have Go 1.22 installed. 159 | 160 | ``` 161 | make build 162 | ``` 163 | 164 | Or building directly: 165 | 166 | ``` 167 | go build -o opa-spicedb . 168 | ``` 169 | 170 | 171 | # Demo ✨ 172 | 173 | > Start authzed demo environment 174 | 175 | ``` 176 | docker compose -f demo/docker-compose.yaml up -d 177 | ``` 178 | 179 | > Run Open Policy Agent with spicedb plugin enabled 180 | 181 | 182 | ``` 183 | ./opa-spicedb run \ 184 | --set plugins.spicedb.endpoint=localhost:50051 \ 185 | --set plugins.spicedb.token=foobar \ 186 | --set plugins.spicedb.insecure=true 187 | ``` 188 | 189 | > or use a configuration file 190 | 191 | ``` 192 | ./opa-spicedb run -c demo/opa-config-demo.yaml 193 | 194 | ``` 195 | 196 | 197 | > Query relations against authzed 198 | > See the [example ReBAC schema](./demo/schema-and-data.yaml) for reference. 199 | 200 | ``` 201 | > spicedb.check_permission("document","firstdoc", "view", "user","alice") 202 | { 203 | "lookedUpAt": "GhUKEzE3MjYwOTIxNjAwMDAwMDAwMDA=", 204 | "result": true 205 | } 206 | 207 | > spicedb.check_permission("document","firstdoc", "edit", "user","bob") 208 | { 209 | "lookedUpAt": "GhUKEzE3MjY2MTcxMzAwMDAwMDAwMDA=", 210 | "result": false 211 | } 212 | > exit 213 | 214 | ``` 215 | 216 | > Stop demo environment 217 | 218 | ``` 219 | docker compose -f demo/docker-compose.yaml down 220 | ``` 221 | 222 | 223 | ## 🤝 Contributing 224 | 225 | This project is a work in progress. 226 | If something is broken or there's a feature that you want, feel free to check [issues page]() and if so inclined submit a PR! 227 | 228 | Contributions, issues and feature requests are welcome.
229 | 230 | Here are some general guidelines:
231 | 232 | * File an issue first prior to submitting a PR! 233 | * Ensure all exported items are properly commented 234 | * If applicable, submit a test suite against your PR 235 | 236 | 237 | 238 | ## Show your support 239 | 240 | Please ⭐️ this repository if this project helped you! 241 | 242 | 243 | ## Authors 244 | 245 | 👤 **Roland Baum** 246 | 247 | - Github: [@tr33](https://github.com/tr33) 248 | 249 | 👤 **umbrella.associates** 250 | 251 | - web: [www.umbrella.associates](https://www.umbrella.associates/) 252 | 253 | 254 | ## Credits 255 | 256 | - [@thomasdarimont](https://github.com/thomasdarimont/) 257 | 258 | 259 | ## 📝 License 260 | 261 | Copyright © 2024 [umbrella.associates](https://github.com/umbrellaassociates).
262 | This project is under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) licensed. 263 | -------------------------------------------------------------------------------- /builtins/builtins.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "github.com/open-policy-agent/opa/rego" 5 | ) 6 | 7 | func Register() { 8 | rego.RegisterBuiltinDyn(checkPermissionBuiltinDecl, checkPermissionBuiltinImpl) 9 | rego.RegisterBuiltinDyn(lookupResourcesBuiltinDecl, lookupResourcesBuiltinImpl) 10 | rego.RegisterBuiltinDyn(lookupSubjectsBuiltinDecl, lookupSubjectsBuiltinImpl) 11 | rego.RegisterBuiltin3(WriteRelationshipsBuiltinDecl, WriteRelationshipsBuiltinImpl) 12 | rego.RegisterBuiltinDyn(ReadRelationshipsBuiltinDecl, ReadRelationshipsBuiltinImpl) 13 | rego.RegisterBuiltinDyn(DeleteRelationshipsBuiltinDecl, DeleteRelationshipsBuiltinImpl) 14 | } 15 | -------------------------------------------------------------------------------- /builtins/check_permission.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | "google.golang.org/grpc/status" 11 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 12 | ) 13 | 14 | var checkPermissionBuiltinDecl = ®o.Function{ 15 | Name: "spicedb.check_permission", 16 | Decl: types.NewFunction( 17 | types.Args(types.S, types.S, types.S, types.S, types.S), // subject, permission, resource 18 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a boolean 19 | Nondeterministic: true, 20 | } 21 | 22 | // Use a custom cache key type to avoid collisions with other builtins caching data!! 23 | type checkPermissionCacheKeyType string 24 | 25 | type checkResult struct { 26 | Token ZedToken `json:"lookedUpAt"` 27 | Result bool `json:"result"` 28 | } 29 | 30 | // checkPermissionBuiltinImpl checks the given permission requests against spicedb. 31 | func checkPermissionBuiltinImpl(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { 32 | var error_result ErrorStruct 33 | 34 | // extract parameters 35 | var resourceType, resourceId, permission, subjectType, subjectId string 36 | 37 | if err := ast.As(terms[0].Value, &resourceType); err != nil { 38 | return nil, err 39 | } 40 | 41 | if err := ast.As(terms[1].Value, &resourceId); err != nil { 42 | return nil, err 43 | } 44 | 45 | if err := ast.As(terms[2].Value, &permission); err != nil { 46 | return nil, err 47 | } 48 | 49 | if err := ast.As(terms[3].Value, &subjectType); err != nil { 50 | return nil, err 51 | } 52 | 53 | if err := ast.As(terms[4].Value, &subjectId); err != nil { 54 | return nil, err 55 | } 56 | 57 | // Check if it is already cached, assume they never become invalid. 58 | var cacheKey = checkPermissionCacheKeyType(fmt.Sprintf("%s:%s#%s@%s:%s", resourceType, resourceId, permission, subjectType, subjectId)) 59 | cached, ok := bctx.Cache.Get(cacheKey) 60 | if ok { 61 | return ast.NewTerm(cached.(ast.Value)), nil 62 | } 63 | 64 | subjectReference := &authzedpb.SubjectReference{Object: &authzedpb.ObjectReference{ 65 | ObjectType: authzed.Schemaprefix + subjectType, 66 | ObjectId: subjectId, 67 | }} 68 | 69 | resourceReference := &authzedpb.ObjectReference{ 70 | ObjectType: authzed.Schemaprefix + resourceType, 71 | ObjectId: resourceId, 72 | } 73 | 74 | client := authzed.GetAuthzedClient() 75 | if client == nil { 76 | return nil, errors.New("authzed client not configured") 77 | } 78 | 79 | resp, err := client.CheckPermission(bctx.Context, &authzedpb.CheckPermissionRequest{ 80 | Resource: resourceReference, 81 | Permission: permission, 82 | Subject: subjectReference, 83 | }) 84 | 85 | if err != nil { // error condition seems NOT to catch issues with the write request 86 | // extract if gRPC error 87 | if s, ok := status.FromError(err); ok { 88 | // Extract code & description 89 | error_result = ErrorStruct{s.Code().String(), s.Message()} 90 | } else { 91 | var errorstring = fmt.Sprintf("%s", err) 92 | error_result = ErrorStruct{"Error", errorstring} 93 | } 94 | 95 | var error_term, _ = ast.InterfaceToValue(error_result) 96 | 97 | return ast.NewTerm(error_term), nil 98 | } 99 | 100 | // extract ZedToken 101 | var token string = resp.CheckedAt.Token 102 | zedtoken := ZedToken(token) 103 | 104 | var has_permissionship bool = resp.Permissionship == authzedpb.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION 105 | 106 | result := checkResult{zedtoken, has_permissionship} 107 | term, err := ast.InterfaceToValue(result) 108 | if err != nil { 109 | return nil, err 110 | } 111 | bctx.Cache.Put(cacheKey, term) 112 | 113 | return ast.NewTerm(term), nil 114 | } 115 | -------------------------------------------------------------------------------- /builtins/delete_relationships.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 11 | ) 12 | 13 | type deleteRelationshipsResult struct { 14 | Result bool `json:"result"` 15 | Token ZedToken `json:"deletedAt"` 16 | } 17 | 18 | var DeleteRelationshipsBuiltinDecl = ®o.Function{ 19 | Name: "spicedb.delete_relationships", 20 | Decl: types.NewFunction( 21 | types.Args(types.S, types.S, types.A, types.S, types.S), // resourceType, resourceId, relationship, subjectType, subjectId 22 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a ObjectType 23 | } 24 | 25 | // Use a custom cache key type to avoid collisions with other builtins caching data!! 26 | type DeleteRelationshipsCacheKeyType string 27 | 28 | // LookupResourcesBuiltinImpl checks the given permission requests against spicedb. 29 | func DeleteRelationshipsBuiltinImpl(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { 30 | 31 | // extract parameters 32 | var resourceType, resourceId, relationship, subjectType, subjectId string 33 | 34 | if err := ast.As(terms[0].Value, &resourceType); err != nil { 35 | fmt.Println("error", err) 36 | return nil, err 37 | } 38 | 39 | if err := ast.As(terms[1].Value, &resourceId); err != nil { 40 | fmt.Println("error", err) 41 | return nil, err 42 | } 43 | 44 | if err := ast.As(terms[2].Value, &relationship); err != nil { 45 | fmt.Println("error", err) 46 | return nil, err 47 | } 48 | 49 | if err := ast.As(terms[3].Value, &subjectType); err != nil { 50 | fmt.Println("error", err) 51 | return nil, err 52 | } 53 | 54 | if err := ast.As(terms[4].Value, &subjectId); err != nil { 55 | fmt.Println("error", err) 56 | return nil, err 57 | } 58 | 59 | // Check if it is already cached, assume they never become invalid. 60 | var cacheKey = DeleteRelationshipsCacheKeyType(fmt.Sprintf("%s:#%s@%s:%s", resourceType, resourceId, relationship, subjectType, subjectId)) 61 | cached, found := bctx.Cache.Get(cacheKey) 62 | if found { 63 | return ast.NewTerm(cached.(ast.Value)), nil 64 | } 65 | 66 | // construct query element: SubjectFilter 67 | 68 | var subjectFilter *authzedpb.SubjectFilter 69 | // fmt.Println ("## 1>", subjectFilter) 70 | if subjectType != "" { 71 | subjectFilter = &authzedpb.SubjectFilter{ 72 | SubjectType: authzed.Schemaprefix + subjectType, 73 | //OptionalSubjectId: subjectId, 74 | // OptionalRelation: ... 75 | } 76 | } 77 | if subjectType != "" && subjectId != "" { 78 | subjectFilter = &authzedpb.SubjectFilter{ 79 | SubjectType: authzed.Schemaprefix + subjectType, 80 | OptionalSubjectId: subjectId, 81 | // OptionalRelation: ... 82 | } 83 | 84 | } 85 | 86 | // construct query element: RelationshipFilter 87 | 88 | relationshipFilter := &authzedpb.RelationshipFilter{ 89 | ResourceType: authzed.Schemaprefix + resourceType, 90 | } 91 | if resourceId != "" { 92 | relationshipFilter.OptionalResourceId = resourceId 93 | } 94 | if subjectFilter != nil { 95 | relationshipFilter.OptionalSubjectFilter = subjectFilter 96 | } 97 | 98 | if relationship != "" { 99 | relationshipFilter.OptionalRelation = relationship 100 | } 101 | 102 | // get client 103 | client := authzed.GetAuthzedClient() 104 | if client == nil { 105 | return nil, errors.New("authzed client not configured") 106 | } 107 | 108 | // do query 109 | resp, err := client.DeleteRelationships(bctx.Context, &authzedpb.DeleteRelationshipsRequest{ 110 | RelationshipFilter: relationshipFilter, 111 | }) 112 | 113 | if err != nil { 114 | var error_term, _ = ast.InterfaceToValue(err) 115 | return ast.NewTerm(error_term), nil 116 | 117 | } 118 | 119 | token := resp.DeletedAt.Token 120 | 121 | result := deleteRelationshipsResult{ 122 | Result: true, 123 | Token: ZedToken(token), 124 | } 125 | // Convert the result into an AST Term 126 | term, err := ast.InterfaceToValue(result) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | bctx.Cache.Put(cacheKey, term) 132 | // 133 | return ast.NewTerm(term), nil 134 | 135 | } 136 | -------------------------------------------------------------------------------- /builtins/lookup_resources.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | "io" 11 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 12 | "google.golang.org/grpc/status" 13 | ) 14 | 15 | type ZedToken string 16 | 17 | type lookupResult struct { 18 | Result bool `json:"result"` 19 | Token ZedToken `json:"lookedUpAt"` 20 | ResourceObjectIds []string `json:"resourceIds"` 21 | ResourceObjectType string `json:"resourceType"` 22 | Permission string `json:"permission"` 23 | SubjectType string `json:"subjectType"` 24 | SubjectId string `json:"subjectId"` 25 | } 26 | 27 | type ErrorStruct struct { 28 | Error string `json:"error"` 29 | Desc string `json:"desc"` 30 | } 31 | 32 | var lookupResourcesBuiltinDecl = ®o.Function{ 33 | Name: "spicedb.lookup_resources", 34 | Decl: types.NewFunction( 35 | types.Args(types.S, types.S, types.S, types.S), // resource, permission, subjectType 36 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a ObjectType 37 | } 38 | 39 | // Use a custom cache key type to avoid collisions with other builtins caching data!! 40 | type lookupResourcesCacheKeyType string 41 | 42 | // LookupResourcesBuiltinImpl checks the given permission requests against spicedb. 43 | func lookupResourcesBuiltinImpl(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { 44 | 45 | // extract parameters 46 | var resourceType, permission, subjectType, subjectId string 47 | 48 | if err := ast.As(terms[0].Value, &resourceType); err != nil { 49 | return nil, err 50 | } 51 | 52 | if err := ast.As(terms[1].Value, &permission); err != nil { 53 | return nil, err 54 | } 55 | 56 | if err := ast.As(terms[2].Value, &subjectType); err != nil { 57 | return nil, err 58 | } 59 | 60 | if err := ast.As(terms[3].Value, &subjectId); err != nil { 61 | return nil, err 62 | } 63 | 64 | // Check if it is already cached, assume they never become invalid. 65 | var cacheKey = lookupResourcesCacheKeyType(fmt.Sprintf("%s:?#%s@%s:%s", resourceType, permission, subjectType, subjectId)) 66 | cached, found := bctx.Cache.Get(cacheKey) 67 | if found { 68 | return ast.NewTerm(cached.(ast.Value)), nil 69 | } 70 | 71 | // construct query element: subjectReference 72 | subjectReference := &authzedpb.SubjectReference{Object: &authzedpb.ObjectReference{ 73 | ObjectType: authzed.Schemaprefix + subjectType, 74 | ObjectId: subjectId, 75 | }} 76 | 77 | // get client 78 | client := authzed.GetAuthzedClient() 79 | if client == nil { 80 | return nil, errors.New("authzed client not configured") 81 | } 82 | 83 | // do query 84 | resp, err := client.LookupResources(bctx.Context, &authzedpb.LookupResourcesRequest{ 85 | ResourceObjectType: authzed.Schemaprefix + resourceType, 86 | Permission: permission, 87 | Subject: subjectReference, 88 | }) 89 | 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | var has_permissionship bool 95 | var resourceIds []string = make([]string, 0) 96 | var token string 97 | var error_result ErrorStruct 98 | 99 | // result is a stream, fetch elements 100 | for { 101 | result, err := resp.Recv() // fetch response element 102 | 103 | if err == io.EOF { // empty 104 | break 105 | } 106 | 107 | if err != nil { // result is an error 108 | // extract if gRPC error 109 | if s, ok := status.FromError(err); ok { 110 | // Extract code & description 111 | error_result = ErrorStruct{s.Code().String(), s.Message()} 112 | } else { 113 | var errorstring = fmt.Sprintf("%s", err) 114 | error_result = ErrorStruct{"Error", errorstring} 115 | } 116 | // don't continue on errors 117 | break 118 | } 119 | 120 | has_permissionship = result.Permissionship == authzedpb.LookupPermissionship_LOOKUP_PERMISSIONSHIP_HAS_PERMISSION 121 | if !has_permissionship == true { // skip if no permission 122 | continue 123 | } 124 | 125 | // append resourceId 126 | resourceIds = append(resourceIds, result.ResourceObjectId) 127 | 128 | if token == "" { // save token 129 | token = result.LookedUpAt.Token 130 | } 131 | } 132 | 133 | // previous for-look broke with error, return error struct 134 | if error_result.Error != "" { 135 | var error_term, _ = ast.InterfaceToValue(error_result) 136 | return ast.NewTerm(error_term), nil 137 | } 138 | 139 | // extract ZedToken 140 | zedtoken := ZedToken(token) 141 | // construct result structure 142 | 143 | result := lookupResult{true, zedtoken, resourceIds, resourceType, permission, subjectType, subjectId} 144 | // Convert the result into an AST Term 145 | term, err := ast.InterfaceToValue(result) 146 | if err != nil { 147 | return nil, err 148 | } 149 | bctx.Cache.Put(cacheKey, term) 150 | 151 | return ast.NewTerm(term), nil 152 | 153 | } 154 | -------------------------------------------------------------------------------- /builtins/lookup_subjects.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | "google.golang.org/grpc/status" 11 | "io" 12 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 13 | ) 14 | 15 | type lookupSubjectsResult struct { 16 | Result bool `json:"result"` 17 | Token ZedToken `json:"lookedUpAt"` 18 | ResourceObjectId string `json:"resourceId"` 19 | ResourceObjectType string `json:"resourceType"` 20 | Permission string `json:"permission"` 21 | SubjectType string `json:"subjectType"` 22 | SubjectIds []string `json:"subjectIds"` 23 | } 24 | 25 | var lookupSubjectsBuiltinDecl = ®o.Function{ 26 | Name: "spicedb.lookup_subjects", 27 | Decl: types.NewFunction( 28 | types.Args(types.S, types.S, types.S, types.S), // resource, permission, subjectType 29 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a ObjectType 30 | } 31 | 32 | // Use a custom cache key type to avoid collisions with other builtins caching data!! 33 | type lookupSubjectsCacheKeyType string 34 | 35 | // lookupSubjectsBuiltinImpl checks the given permission requests against spicedb. 36 | func lookupSubjectsBuiltinImpl(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { 37 | 38 | // extract parameters 39 | var resourceType, resourceId, permission, subjectType string 40 | 41 | if err := ast.As(terms[0].Value, &resourceType); err != nil { 42 | return nil, err 43 | } 44 | 45 | if err := ast.As(terms[1].Value, &resourceId); err != nil { 46 | return nil, err 47 | } 48 | 49 | if err := ast.As(terms[2].Value, &permission); err != nil { 50 | return nil, err 51 | } 52 | 53 | if err := ast.As(terms[3].Value, &subjectType); err != nil { 54 | return nil, err 55 | } 56 | 57 | // construct query element: resourceReference 58 | ResourceReference := &authzedpb.ObjectReference{ 59 | ObjectType: authzed.Schemaprefix + resourceType, 60 | ObjectId: resourceId, 61 | } 62 | 63 | // Check if it is already cached, assume they never become invalid. 64 | var cacheKey = lookupSubjectsCacheKeyType(fmt.Sprintf("%s:%s#%s@%s:?", resourceType, resourceId, permission, subjectType)) 65 | cached, found := bctx.Cache.Get(cacheKey) 66 | if found { 67 | return ast.NewTerm(cached.(ast.Value)), nil 68 | } 69 | 70 | // get client 71 | client := authzed.GetAuthzedClient() 72 | if client == nil { 73 | return nil, errors.New("authzed client not configured") 74 | } 75 | 76 | // do query 77 | resp, err := client.LookupSubjects(bctx.Context, &authzedpb.LookupSubjectsRequest{ 78 | Resource: ResourceReference, 79 | Permission: permission, 80 | SubjectObjectType: authzed.Schemaprefix + subjectType, 81 | }) 82 | 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | var has_permissionship bool 88 | var subjectIds []string = make([]string, 0) 89 | var token string 90 | var error_result ErrorStruct 91 | 92 | // result is a stream, fetch elements 93 | for { 94 | result, err := resp.Recv() // fetch response element 95 | 96 | if err == io.EOF { // empty 97 | break 98 | } 99 | 100 | if err != nil { // result is an error 101 | // extract if gRPC error 102 | if s, ok := status.FromError(err); ok { 103 | // Extract code & description 104 | error_result = ErrorStruct{s.Code().String(), s.Message()} 105 | } else { 106 | var errorstring = fmt.Sprintf("%s", err) 107 | error_result = ErrorStruct{"Error", errorstring} 108 | } 109 | // don't continue on errors 110 | break 111 | } 112 | 113 | has_permissionship = result.Permissionship == authzedpb.LookupPermissionship_LOOKUP_PERMISSIONSHIP_HAS_PERMISSION 114 | if !has_permissionship == true { // skip if no permission 115 | continue 116 | } 117 | 118 | // append resourceId 119 | subjectIds = append(subjectIds, result.SubjectObjectId) 120 | 121 | if token == "" { // save token 122 | token = result.LookedUpAt.Token 123 | } 124 | } 125 | 126 | // previous for-look broke with error, return error struct 127 | if error_result.Error != "" { 128 | var error_term, _ = ast.InterfaceToValue(error_result) 129 | return ast.NewTerm(error_term), nil 130 | } 131 | 132 | // extract ZedToken 133 | zedtoken := ZedToken(token) 134 | 135 | // construct result structure 136 | 137 | result := lookupSubjectsResult{true, zedtoken, resourceType, resourceId, permission, subjectType, subjectIds} 138 | // Convert the result into an AST Term 139 | term, err := ast.InterfaceToValue(result) 140 | if err != nil { 141 | return nil, err 142 | } 143 | bctx.Cache.Put(cacheKey, term) 144 | 145 | return ast.NewTerm(term), nil 146 | 147 | } 148 | -------------------------------------------------------------------------------- /builtins/read_relationships.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | "google.golang.org/grpc/status" 11 | "io" 12 | "strings" 13 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 14 | ) 15 | 16 | type Relationship struct { 17 | ResourceType string `json:"resourceType"` 18 | ResourceId string `json:"resourceId"` 19 | Relationship string `json:"relationship"` 20 | SubjectType string `json:"subjectType"` 21 | SubjectId string `json:"subjectId"` 22 | } 23 | 24 | type readRelationshipsResult struct { 25 | Result bool `json:"result"` 26 | Token ZedToken `json:"lookedUpAt"` 27 | Relationships []Relationship `json:"relationships"` 28 | } 29 | 30 | var ReadRelationshipsBuiltinDecl = ®o.Function{ 31 | Name: "spicedb.read_relationships", 32 | Decl: types.NewFunction( 33 | types.Args(types.S, types.S, types.A, types.S, types.S), // resourceType, resourceId, permission, subjectType, subjectId 34 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a ObjectType 35 | } 36 | 37 | // Use a custom cache key type to avoid collisions with other builtins caching data!! 38 | type ReadRelationshipsCacheKeyType string 39 | 40 | // LookupResourcesBuiltinImpl checks the given permission requests against spicedb. 41 | func ReadRelationshipsBuiltinImpl(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) { 42 | 43 | // extract parameters 44 | var resourceType, resourceId, permission, subjectType, subjectId string 45 | 46 | if err := ast.As(terms[0].Value, &resourceType); err != nil { 47 | fmt.Println("error", err) 48 | return nil, err 49 | } 50 | 51 | if err := ast.As(terms[1].Value, &resourceId); err != nil { 52 | fmt.Println("error", err) 53 | return nil, err 54 | } 55 | 56 | if err := ast.As(terms[2].Value, &permission); err != nil { 57 | fmt.Println("error", err) 58 | return nil, err 59 | } 60 | 61 | if err := ast.As(terms[3].Value, &subjectType); err != nil { 62 | fmt.Println("error", err) 63 | return nil, err 64 | } 65 | if err := ast.As(terms[4].Value, &subjectId); err != nil { 66 | fmt.Println("error", err) 67 | return nil, err 68 | } 69 | 70 | // Check if it is already cached, assume they never become invalid. 71 | var cacheKey = ReadRelationshipsCacheKeyType(fmt.Sprintf("%s:#%s@%s:%s", resourceType, resourceId, permission, subjectType, subjectId)) 72 | cached, found := bctx.Cache.Get(cacheKey) 73 | if found { 74 | return ast.NewTerm(cached.(ast.Value)), nil 75 | } 76 | 77 | // construct query element: SubjectFilter 78 | 79 | var subjectFilter *authzedpb.SubjectFilter 80 | if subjectType != "" { 81 | subjectFilter = &authzedpb.SubjectFilter{ 82 | SubjectType: authzed.Schemaprefix + subjectType, 83 | //OptionalSubjectId: subjectId, 84 | // OptionalRelation: ... 85 | } 86 | } 87 | if subjectType != "" && subjectId != "" { 88 | subjectFilter = &authzedpb.SubjectFilter{ 89 | SubjectType: authzed.Schemaprefix + subjectType, 90 | OptionalSubjectId: subjectId, 91 | // OptionalRelation: ... 92 | } 93 | 94 | } 95 | 96 | // construct query element: RelationshipFilter 97 | 98 | relationshipFilter := &authzedpb.RelationshipFilter{ 99 | ResourceType: authzed.Schemaprefix + resourceType, 100 | } 101 | if resourceId != "" { 102 | relationshipFilter.OptionalResourceId = resourceId 103 | } 104 | if subjectFilter != nil { 105 | relationshipFilter.OptionalSubjectFilter = subjectFilter 106 | } 107 | 108 | if permission != "" { 109 | relationshipFilter.OptionalRelation = permission 110 | } 111 | 112 | // get client 113 | client := authzed.GetAuthzedClient() 114 | if client == nil { 115 | return nil, errors.New("authzed client not configured") 116 | } 117 | 118 | // do query 119 | resp, err := client.ReadRelationships(bctx.Context, &authzedpb.ReadRelationshipsRequest{ 120 | RelationshipFilter: relationshipFilter, 121 | }) 122 | 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | var readResult = readRelationshipsResult{ 128 | Result: true, 129 | } 130 | var token string 131 | var error_result ErrorStruct 132 | 133 | // result is a stream, fetch elements 134 | for { 135 | result, err := resp.Recv() // fetch response element 136 | 137 | if err == io.EOF { // empty 138 | break 139 | } 140 | 141 | if err != nil { // result is an error 142 | // extract if gRPC error 143 | if s, ok := status.FromError(err); ok { 144 | // Extract code & description 145 | error_result = ErrorStruct{s.Code().String(), s.Message()} 146 | } else { 147 | var errorstring = fmt.Sprintf("%s", err) 148 | error_result = ErrorStruct{"Error", errorstring} 149 | } 150 | // don't continue on errors 151 | break 152 | } 153 | 154 | relation := Relationship{ 155 | ResourceType: strings.TrimPrefix(result.Relationship.Resource.ObjectType, authzed.Schemaprefix), 156 | ResourceId: result.Relationship.Resource.ObjectId, 157 | Relationship: result.Relationship.Relation, 158 | SubjectType: strings.TrimPrefix(result.Relationship.Subject.Object.ObjectType, authzed.Schemaprefix), 159 | SubjectId: result.Relationship.Subject.Object.ObjectId, 160 | } 161 | // append resourceId 162 | readResult.Relationships = append(readResult.Relationships, relation) 163 | 164 | if token == "" { // save token 165 | token = result.ReadAt.Token 166 | } 167 | } 168 | 169 | // previous for-look broke with error, return error struct 170 | if error_result.Error != "" { 171 | var error_term, _ = ast.InterfaceToValue(error_result) 172 | return ast.NewTerm(error_term), nil 173 | } 174 | 175 | // extract ZedToken 176 | readResult.Token = ZedToken(token) 177 | 178 | // Convert the result into an AST Term 179 | term, err := ast.InterfaceToValue(readResult) 180 | if err != nil { 181 | return nil, err 182 | } 183 | 184 | bctx.Cache.Put(cacheKey, term) 185 | 186 | return ast.NewTerm(term), nil 187 | 188 | } 189 | -------------------------------------------------------------------------------- /builtins/write_relationships.go: -------------------------------------------------------------------------------- 1 | package builtins 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | authzedpb "github.com/authzed/authzed-go/proto/authzed/api/v1" 7 | "github.com/open-policy-agent/opa/ast" 8 | "github.com/open-policy-agent/opa/rego" 9 | "github.com/open-policy-agent/opa/types" 10 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 11 | "google.golang.org/grpc/status" 12 | ) 13 | 14 | type writeRelationshipsResult struct { 15 | Token ZedToken `json:"writtenAt"` 16 | Result bool `json:"result"` 17 | } 18 | 19 | var WriteRelationshipsBuiltinDecl = ®o.Function{ 20 | Name: "spicedb.write_relationships", 21 | Decl: types.NewFunction( 22 | types.Args( 23 | types.Named("writes", 24 | types.NewAny( 25 | types.NewArray(nil, types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 26 | types.NewSet(types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 27 | ), 28 | ), 29 | types.Named("updates", 30 | types.NewAny( 31 | types.NewArray(nil, types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 32 | types.NewSet(types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 33 | ), 34 | ), 35 | types.Named("deletes", 36 | types.NewAny( 37 | types.NewArray(nil, types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 38 | types.NewSet(types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), 39 | ), 40 | ), 41 | ), 42 | types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))), // Returns a structure 43 | Nondeterministic: true, 44 | } 45 | 46 | func convertToArray(term *ast.Term) (*ast.Array, error) { 47 | // Convert the Term to an Array 48 | 49 | collection := term.Value 50 | 51 | var array *ast.Array 52 | 53 | // Convert the input to an ast.Array if it is an ast.Set, otherwise use the array directly 54 | switch c := collection.(type) { 55 | case *ast.Array: 56 | array = c 57 | case ast.Set: 58 | // Convert ast.Set to ast.Array 59 | // todo: try to avoid extra iteration 60 | array = ast.NewArray() 61 | c.Foreach(func(elem *ast.Term) { 62 | array = array.Append(elem) 63 | }) 64 | default: 65 | return nil, fmt.Errorf("expected array or set, got %v", collection) 66 | } 67 | 68 | return array, nil 69 | } 70 | 71 | func generateAuthzedOperationTupel(operationStr string, tupels []relationshipStruct) ([]*authzedpb.RelationshipUpdate, error) { 72 | var updateRelationships []*authzedpb.RelationshipUpdate 73 | var update_operation authzedpb.RelationshipUpdate_Operation 74 | 75 | 76 | switch operationStr { 77 | case "TOUCH": 78 | update_operation = authzedpb.RelationshipUpdate_OPERATION_TOUCH 79 | case "WRITE": 80 | update_operation = authzedpb.RelationshipUpdate_OPERATION_CREATE 81 | case "DELETE": 82 | update_operation = authzedpb.RelationshipUpdate_OPERATION_DELETE 83 | default: 84 | update_operation = authzedpb.RelationshipUpdate_OPERATION_UNSPECIFIED 85 | } 86 | 87 | // Iterate over input tupels 88 | for _, update_tupel := range tupels { 89 | 90 | if update_tupel.ResourceType == "" { 91 | return nil, fmt.Errorf("resoureType not set: '%s'", update_tupel) 92 | } 93 | if update_tupel.ResourceId == "" { 94 | return nil, fmt.Errorf("resoureId not set: '%s'", update_tupel) 95 | } 96 | if update_tupel.Relationship == "" { 97 | return nil, fmt.Errorf("relationship not set: '%s'", update_tupel) 98 | } 99 | if update_tupel.SubjectType == "" { 100 | return nil, fmt.Errorf("subjectType not set: '%s'", update_tupel) 101 | } 102 | if update_tupel.SubjectId == "" { 103 | return nil, fmt.Errorf("subjectId not set: '%s'", update_tupel) 104 | } 105 | 106 | resourceReference := &authzedpb.ObjectReference{ 107 | ObjectType: authzed.Schemaprefix + update_tupel.ResourceType, 108 | ObjectId: update_tupel.ResourceId, 109 | } 110 | 111 | relationship := update_tupel.Relationship 112 | 113 | subjectReference := &authzedpb.SubjectReference{Object: &authzedpb.ObjectReference{ 114 | ObjectType: authzed.Schemaprefix + update_tupel.SubjectType, 115 | ObjectId: update_tupel.SubjectId, 116 | }} 117 | 118 | 119 | relationshipStruct := &authzedpb.Relationship{ 120 | Resource: resourceReference, 121 | Relation: relationship, 122 | Subject: subjectReference, 123 | } 124 | 125 | updateTupel := &authzedpb.RelationshipUpdate{ 126 | Operation: update_operation, 127 | Relationship: relationshipStruct, 128 | } 129 | 130 | updateRelationships = append(updateRelationships, updateTupel) 131 | 132 | } 133 | 134 | return updateRelationships, nil 135 | } 136 | 137 | func renderErr(err error) *ast.Term { 138 | error_result := ErrorStruct{"Error", fmt.Sprintf("%s", err)} 139 | var error_term, _ = ast.InterfaceToValue(error_result) 140 | return ast.NewTerm(error_term) 141 | } 142 | 143 | type relationshipStruct struct { 144 | ResourceType string `json:"resourceType"` 145 | ResourceId string `json:"resourceId"` 146 | Relationship string `json:"relationship"` 147 | SubjectType string `json:"subjectType"` 148 | SubjectId string `json:"subjectId"` 149 | } 150 | 151 | // WriteRelationshipsBuiltinImpl writes/updates a set of given relationships against spicedb. 152 | func WriteRelationshipsBuiltinImpl(bctx rego.BuiltinContext, writesTerm, touchesTerm, deletesTerm *ast.Term) (*ast.Term, error) { 153 | var arrayTerm *ast.Array 154 | 155 | // 156 | // convert writesTerm 157 | // Ensure the argument is either an array or a set 158 | // 159 | if array, err := convertToArray(writesTerm); err != nil { 160 | return renderErr(err), nil 161 | } else { 162 | arrayTerm = array 163 | } 164 | 165 | var writesRelStr []relationshipStruct 166 | if err := ast.As(arrayTerm, &writesRelStr); err != nil { 167 | return renderErr(err), nil 168 | } 169 | 170 | fmt.Println(authzed.Schemaprefix) 171 | // 172 | // convert touchesTerm 173 | // Ensure the argument is either an array or a set 174 | // 175 | if array, err := convertToArray(touchesTerm); err != nil { 176 | return renderErr(err), nil 177 | } else { 178 | arrayTerm = array 179 | } 180 | 181 | var touchesRelStr []relationshipStruct 182 | if err := ast.As(arrayTerm, &touchesRelStr); err != nil { 183 | return renderErr(err), nil 184 | } 185 | // 186 | // convert deletesTerm 187 | // Ensure the argument is either an array or a set 188 | // 189 | if array, err := convertToArray(deletesTerm); err != nil { 190 | return renderErr(err), nil 191 | } else { 192 | arrayTerm = array 193 | } 194 | var deletesRelStr []relationshipStruct 195 | if err := ast.As(arrayTerm, &deletesRelStr); err != nil { 196 | return renderErr(err), nil 197 | } 198 | 199 | var error_result ErrorStruct 200 | 201 | var updateRelationships []*authzedpb.RelationshipUpdate 202 | updates, err := generateAuthzedOperationTupel("WRITE", writesRelStr) 203 | if err != nil { 204 | return renderErr(err), nil 205 | } 206 | updateRelationships = append(updateRelationships, updates...) 207 | 208 | updates, err = generateAuthzedOperationTupel("TOUCH", touchesRelStr) 209 | if err != nil { 210 | return renderErr(err), nil 211 | } 212 | updateRelationships = append(updateRelationships, updates...) 213 | 214 | updates, err = generateAuthzedOperationTupel("DELETE", deletesRelStr) 215 | if err != nil { 216 | return renderErr(err), nil 217 | 218 | } 219 | updateRelationships = append(updateRelationships, updates...) 220 | 221 | writeRequest := &authzedpb.WriteRelationshipsRequest{ 222 | Updates: updateRelationships, 223 | } 224 | fmt.Println(writeRequest) 225 | 226 | // get client 227 | client := authzed.GetAuthzedClient() 228 | if client == nil { 229 | return nil, errors.New("authzed client not configured") 230 | } 231 | 232 | // do query 233 | response, err := client.WriteRelationships(bctx.Context, writeRequest) 234 | 235 | if err != nil { // error condition seems NOT to catch issues with the write request 236 | // extract if gRPC error 237 | if s, ok := status.FromError(err); ok { 238 | // Extract code & description 239 | error_result = ErrorStruct{s.Code().String(), s.Message()} 240 | } else { 241 | var errorstring = fmt.Sprintf("%s", err) 242 | error_result = ErrorStruct{"Error", errorstring} 243 | } 244 | 245 | var error_term, _ = ast.InterfaceToValue(error_result) 246 | return ast.NewTerm(error_term), nil 247 | } 248 | // extract ZedToken 249 | var token string = response.WrittenAt.Token 250 | zedtoken := ZedToken(token) 251 | 252 | // construct result structure 253 | result := writeRelationshipsResult{zedtoken, true} 254 | // Convert the result into an AST Term 255 | term, err := ast.InterfaceToValue(result) 256 | if err != nil { 257 | return nil, err 258 | } 259 | 260 | return ast.NewTerm(term), nil 261 | 262 | } 263 | -------------------------------------------------------------------------------- /demo/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | spicedb: 3 | image: "authzed/spicedb" 4 | command: 'serve' 5 | restart: "always" 6 | ports: 7 | - "28080:8080" 8 | - "29090:9090" 9 | - "50051:50051" 10 | environment: 11 | - "SPICEDB_GRPC_PRESHARED_KEY=foobar" 12 | 13 | ## just import schema & demo data 14 | spicedb-import: 15 | image: "authzed/zed" 16 | command: "import --schema=true /schema-and-data.yaml" 17 | environment: 18 | - "ZED_ENDPOINT=spicedb:50051" 19 | - "ZED_TOKEN=foobar" 20 | - "ZED_INSECURE=1" 21 | 22 | volumes: 23 | - ./schema-and-data.yaml:/schema-and-data.yaml:z 24 | depends_on: 25 | - spicedb 26 | links: 27 | - spicedb 28 | -------------------------------------------------------------------------------- /demo/opa-config-demo.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | spicedb: 3 | endpoint: localhost:50051 4 | token: foobar 5 | insecure: true 6 | schemaprefix: 7 | -------------------------------------------------------------------------------- /demo/policy-demo.rego: -------------------------------------------------------------------------------- 1 | spicedb.check_permission("document", "firstdoc", "view", "user", "alice") 2 | 3 | spicedb.lookup_resources("document", "view", "user", "alice") 4 | 5 | spicedb.read_relationships("document", "", "", "", "") 6 | -------------------------------------------------------------------------------- /demo/schema-and-data.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | schema: |- 3 | /** 4 | * user represents a user that can be granted role(s) 5 | */ 6 | definition user {} 7 | 8 | /** 9 | * document represents a document. 10 | */ 11 | definition document { 12 | /** 13 | * writer indicates that the user is a writer on the document. 14 | */ 15 | relation writer: user 16 | 17 | /** 18 | * reader indicates that the user is a reader on the document. 19 | */ 20 | relation reader: user 21 | 22 | /** 23 | * edit indicates that the user has permission to edit the document. 24 | */ 25 | permission edit = writer 26 | 27 | /** 28 | * view indicates that the user has permission to view the document, if they 29 | * are a `reader` *or* have `edit` permission. 30 | */ 31 | permission view = reader + edit 32 | } 33 | 34 | relationships: |- 35 | document:firstdoc#writer@user:alice 36 | document:firstdoc#reader@user:bob 37 | document:seconddoc#reader@user:alice 38 | 39 | assertions: 40 | assertTrue: 41 | - "document:firstdoc#view@user:alice" 42 | - "document:firstdoc#view@user:bob" 43 | - "document:seconddoc#view@user:alice" 44 | assertFalse: 45 | - "document:seconddoc#view@user:bob" 46 | 47 | validation: 48 | document:firstdoc#view: 49 | - "[user:alice] is " 50 | - "[user:bob] is " 51 | document:seconddoc#view: 52 | - "[user:alice] is " 53 | -------------------------------------------------------------------------------- /doc/opa-config.yaml.example: -------------------------------------------------------------------------------- 1 | plugins: 2 | spicedb: 3 | endpoint: spicedb-grpc-addr:443 4 | token: 5 | insecure: false 6 | schemaprefix: myprefix/ 7 | -------------------------------------------------------------------------------- /doc/opa-spicedb-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umbrellaassociates/opa-spicedb/046da758b1ec77ee977bf0a95fd663a08ab5bcda/doc/opa-spicedb-demo.gif -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/umbrellaassociates/opa-spicedb 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/authzed/authzed-go v0.14.0 7 | github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b 8 | github.com/open-policy-agent/opa v0.68.0 9 | google.golang.org/grpc v1.66.0 10 | ) 11 | 12 | require ( 13 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect 14 | github.com/OneOfOne/xxhash v1.2.8 // indirect 15 | github.com/agnivade/levenshtein v1.1.1 // indirect 16 | github.com/beorn7/perks v1.0.1 // indirect 17 | github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect 18 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 19 | github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect 20 | github.com/cespare/xxhash v1.1.0 // indirect 21 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 22 | github.com/containerd/containerd v1.7.21 // indirect 23 | github.com/containerd/errdefs v0.1.0 // indirect 24 | github.com/containerd/log v0.1.0 // indirect 25 | github.com/containerd/platforms v0.2.1 // indirect 26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 27 | github.com/dgraph-io/badger/v3 v3.2103.5 // indirect 28 | github.com/dgraph-io/ristretto v0.1.1 // indirect 29 | github.com/dustin/go-humanize v1.0.1 // indirect 30 | github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect 31 | github.com/felixge/httpsnoop v1.0.4 // indirect 32 | github.com/fsnotify/fsnotify v1.7.0 // indirect 33 | github.com/go-ini/ini v1.67.0 // indirect 34 | github.com/go-logr/logr v1.4.2 // indirect 35 | github.com/go-logr/stdr v1.2.2 // indirect 36 | github.com/gobwas/glob v0.2.3 // indirect 37 | github.com/gogo/protobuf v1.3.2 // indirect 38 | github.com/golang/glog v1.2.2 // indirect 39 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 40 | github.com/golang/protobuf v1.5.4 // indirect 41 | github.com/golang/snappy v0.0.4 // indirect 42 | github.com/google/flatbuffers v24.3.25+incompatible // indirect 43 | github.com/google/uuid v1.6.0 // indirect 44 | github.com/gorilla/mux v1.8.1 // indirect 45 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect 46 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect 47 | github.com/hashicorp/hcl v1.0.0 // indirect 48 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 49 | github.com/jzelinskie/stringz v0.0.3 // indirect 50 | github.com/klauspost/compress v1.17.9 // indirect 51 | github.com/magiconair/properties v1.8.7 // indirect 52 | github.com/mattn/go-runewidth v0.0.16 // indirect 53 | github.com/mitchellh/mapstructure v1.5.0 // indirect 54 | github.com/moby/locker v1.0.1 // indirect 55 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 56 | github.com/olekukonko/tablewriter v0.0.5 // indirect 57 | github.com/opencontainers/go-digest v1.0.0 // indirect 58 | github.com/opencontainers/image-spec v1.1.0 // indirect 59 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 60 | github.com/peterh/liner v1.2.2 // indirect 61 | github.com/pkg/errors v0.9.1 // indirect 62 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect 63 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 64 | github.com/prometheus/client_golang v1.20.2 // indirect 65 | github.com/prometheus/client_model v0.6.1 // indirect 66 | github.com/prometheus/common v0.55.0 // indirect 67 | github.com/prometheus/procfs v0.15.1 // indirect 68 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 69 | github.com/rivo/uniseg v0.4.7 // indirect 70 | github.com/sagikazarmark/locafero v0.6.0 // indirect 71 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 72 | github.com/samber/lo v1.47.0 // indirect 73 | github.com/sergi/go-diff v1.3.1 // indirect 74 | github.com/sirupsen/logrus v1.9.3 // indirect 75 | github.com/sourcegraph/conc v0.3.0 // indirect 76 | github.com/spf13/afero v1.11.0 // indirect 77 | github.com/spf13/cast v1.7.0 // indirect 78 | github.com/spf13/cobra v1.8.1 // indirect 79 | github.com/spf13/pflag v1.0.5 // indirect 80 | github.com/spf13/viper v1.19.0 // indirect 81 | github.com/stretchr/testify v1.9.0 // indirect 82 | github.com/subosito/gotenv v1.6.0 // indirect 83 | github.com/tchap/go-patricia/v2 v2.3.1 // indirect 84 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 85 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 86 | github.com/yashtewari/glob-intersection v0.2.0 // indirect 87 | go.opencensus.io v0.24.0 // indirect 88 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 89 | go.opentelemetry.io/otel v1.28.0 // indirect 90 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 91 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect 92 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 93 | go.opentelemetry.io/otel/sdk v1.28.0 // indirect 94 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 95 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 96 | go.uber.org/automaxprocs v1.5.3 // indirect 97 | go.uber.org/multierr v1.11.0 // indirect 98 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect 99 | golang.org/x/net v0.28.0 // indirect 100 | golang.org/x/sync v0.8.0 // indirect 101 | golang.org/x/sys v0.24.0 // indirect 102 | golang.org/x/text v0.17.0 // indirect 103 | golang.org/x/time v0.6.0 // indirect 104 | google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect 105 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect 106 | google.golang.org/protobuf v1.34.2 // indirect 107 | gopkg.in/ini.v1 v1.67.0 // indirect 108 | gopkg.in/yaml.v2 v2.4.0 // indirect 109 | gopkg.in/yaml.v3 v3.0.1 // indirect 110 | oras.land/oras-go/v2 v2.5.0 // indirect 111 | sigs.k8s.io/yaml v1.4.0 // indirect 112 | ) 113 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= 3 | cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= 4 | cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= 5 | cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= 6 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= 7 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 8 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 9 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 10 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 11 | github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= 12 | github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= 13 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 14 | github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= 15 | github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= 16 | github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= 17 | github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= 18 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= 19 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= 20 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 21 | github.com/authzed/authzed-go v0.14.0 h1:Lvy0qudgdunuQmzsrO9ljNeCr7Cdfh2x1RwgOPi+M9w= 22 | github.com/authzed/authzed-go v0.14.0/go.mod h1:ZyMR4heb6r5t3LJSu84AoxFXQUtaE+nYBbIvBx6vz5s= 23 | github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b h1:wbh8IK+aMLTCey9sZasO7b6BWLAJnHHvb79fvWCXwxw= 24 | github.com/authzed/grpcutil v0.0.0-20240123194739-2ea1e3d2d98b/go.mod h1:s3qC7V7XIbiNWERv7Lfljy/Lx25/V1Qlexb0WJuA8uQ= 25 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 26 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 27 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 28 | github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= 29 | github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= 30 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 31 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 32 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 33 | github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= 34 | github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= 35 | github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= 36 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 37 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 38 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 39 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 41 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 42 | github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= 43 | github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= 44 | github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= 45 | github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= 46 | github.com/containerd/containerd v1.7.21 h1:USGXRK1eOC/SX0L195YgxTHb0a00anxajOzgfN0qrCA= 47 | github.com/containerd/containerd v1.7.21/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g= 48 | github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= 49 | github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= 50 | github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= 51 | github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= 52 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 53 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 54 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 55 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 56 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 57 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 58 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 59 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 60 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 61 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 62 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 63 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 64 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 65 | github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= 66 | github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= 67 | github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= 68 | github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= 69 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 70 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= 71 | github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 72 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= 73 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 74 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 75 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 76 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 77 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 78 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 79 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 80 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 81 | github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= 82 | github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= 83 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 84 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 85 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 86 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 87 | github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= 88 | github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= 89 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 90 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 91 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 92 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 93 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 94 | github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= 95 | github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 96 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 97 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 98 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 99 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 100 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 101 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 102 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 103 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 104 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 105 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 106 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 107 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 108 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 109 | github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= 110 | github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= 111 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 112 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 113 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 114 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 115 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 116 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 117 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 118 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 119 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 120 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 121 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 122 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 123 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 124 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 125 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 126 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 127 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 128 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 129 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 130 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 131 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 132 | github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 133 | github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= 134 | github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 135 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 136 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 137 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 138 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 139 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 140 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 141 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 142 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 143 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 144 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 145 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 146 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 147 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 148 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 149 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 150 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= 151 | github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= 152 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 h1:CWyXh/jylQWp2dtiV33mY4iSSp6yf4lmn+c7/tN+ObI= 153 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0/go.mod h1:nCLIt0w3Ept2NwF8ThLmrppXsfT07oC8k0XNDxd8sVU= 154 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 155 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 156 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 157 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 158 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 159 | github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfUXas= 160 | github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= 161 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 162 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 163 | github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= 164 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 165 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 166 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 167 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 168 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 169 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 170 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 171 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 172 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 173 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 174 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 175 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 176 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 177 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 178 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 179 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 180 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 181 | github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= 182 | github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= 183 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 184 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 185 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 186 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 187 | github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= 188 | github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= 189 | github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= 190 | github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= 191 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 192 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 193 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 194 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 195 | github.com/open-policy-agent/opa v0.67.1 h1:rzy26J6g1X+CKknAcx0Vfbt41KqjuSzx4E0A8DAZf3E= 196 | github.com/open-policy-agent/opa v0.67.1/go.mod h1:aqKlHc8E2VAAylYE9x09zJYr/fYzGX+JKne89UGqFzk= 197 | github.com/open-policy-agent/opa v0.68.0 h1:Jl3U2vXRjwk7JrHmS19U3HZO5qxQRinQbJ2eCJYSqJQ= 198 | github.com/open-policy-agent/opa v0.68.0/go.mod h1:5E5SvaPwTpwt2WM177I9Z3eT7qUpmOGjk1ZdHs+TZ4w= 199 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 200 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 201 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 202 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 203 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 204 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 205 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 206 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 207 | github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= 208 | github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= 209 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 210 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 211 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 212 | github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA= 213 | github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 214 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= 215 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 216 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 217 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 218 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 219 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 220 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 221 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= 222 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= 223 | github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg= 224 | github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 225 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 226 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 227 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 228 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 229 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 230 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 231 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 232 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= 233 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 234 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 235 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 236 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 237 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 238 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 239 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 240 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 241 | github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= 242 | github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= 243 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 244 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 245 | github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= 246 | github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= 247 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 248 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 249 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 250 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 251 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 252 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 253 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 254 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 255 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 256 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 257 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 258 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 259 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 260 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 261 | github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= 262 | github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 263 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 264 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 265 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 266 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 267 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 268 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 269 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 270 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 271 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 272 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 273 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 274 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 275 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 276 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 277 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 278 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 279 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 280 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 281 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 282 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 283 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 284 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 285 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 286 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 287 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 288 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 289 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 290 | github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= 291 | github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= 292 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 293 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 294 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 295 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 296 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 297 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 298 | github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= 299 | github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= 300 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 301 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 302 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 303 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 304 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 305 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= 306 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= 307 | go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= 308 | go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= 309 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= 310 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= 311 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= 312 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= 313 | go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= 314 | go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= 315 | go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= 316 | go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= 317 | go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= 318 | go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= 319 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 320 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 321 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 322 | go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= 323 | go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 324 | go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= 325 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 326 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 327 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 328 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 329 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 330 | go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 331 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 332 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 333 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 334 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 335 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 336 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= 337 | golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= 338 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 339 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 340 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 341 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 342 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 343 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 344 | golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= 345 | golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 346 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 347 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 348 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 349 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 350 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 351 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 352 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 353 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 354 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 355 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 356 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 357 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 358 | golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= 359 | golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 360 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 361 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 362 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 363 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 364 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 365 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 366 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 367 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 368 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 369 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 370 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 371 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 376 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 377 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 378 | golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 379 | golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= 380 | golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 381 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 382 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 383 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 384 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 385 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 386 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 387 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 388 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 389 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 390 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 391 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 392 | golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 393 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 394 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 395 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 396 | golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= 397 | golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 398 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 399 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 400 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 401 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 402 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 403 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 404 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 405 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 406 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 407 | google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 408 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 409 | google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= 410 | google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= 411 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= 412 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 413 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 414 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 415 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 416 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 417 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 418 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 419 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 420 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 421 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 422 | google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= 423 | google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= 424 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 425 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 426 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 427 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 428 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 429 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 430 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 431 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 432 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 433 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 434 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 435 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 436 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 437 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 438 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 439 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 440 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 441 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 442 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 443 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 444 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 445 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 446 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 447 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 448 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 449 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 450 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 451 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 452 | oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= 453 | oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= 454 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 455 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 456 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/open-policy-agent/opa/cmd" 6 | "os" 7 | "umbrella-associates/opa-spicedb/builtins" 8 | "umbrella-associates/opa-spicedb/plugins" 9 | ) 10 | 11 | func main() { 12 | builtins.Register() 13 | plugins.Register() 14 | 15 | if err := cmd.RootCommand.Execute(); err != nil { 16 | fmt.Println(err) 17 | os.Exit(1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /plugins/plugins.go: -------------------------------------------------------------------------------- 1 | package plugins 2 | 3 | import ( 4 | "github.com/open-policy-agent/opa/runtime" 5 | authzed "umbrella-associates/opa-spicedb/plugins/spicedb" 6 | ) 7 | 8 | func Register() { 9 | runtime.RegisterPlugin(authzed.PluginName, authzed.Factory{}) 10 | } 11 | -------------------------------------------------------------------------------- /plugins/spicedb/plugin.go: -------------------------------------------------------------------------------- 1 | package spicedb 2 | 3 | import ( 4 | "context" 5 | "github.com/authzed/authzed-go/v1" 6 | "github.com/authzed/grpcutil" 7 | "github.com/open-policy-agent/opa/plugins" 8 | "github.com/open-policy-agent/opa/util" 9 | "google.golang.org/grpc" 10 | "sync" 11 | ) 12 | 13 | const PluginName = "spicedb" 14 | 15 | type Config struct { 16 | Endpoint string `json:"endpoint"` 17 | Insecure bool `json:"insecure"` 18 | Token string `json:"token"` 19 | Schemaprefix string `json:"schemaprefix"` 20 | } 21 | 22 | type SpicedbPlugin struct { 23 | manager *plugins.Manager 24 | mtx sync.Mutex 25 | config Config 26 | client *authzed.Client 27 | } 28 | 29 | var instance *SpicedbPlugin = nil 30 | var Schemaprefix string = "" 31 | 32 | func GetAuthzedClient() *authzed.Client { 33 | 34 | if instance == nil { 35 | return nil 36 | } 37 | 38 | instance.mtx.Lock() 39 | defer instance.mtx.Unlock() 40 | 41 | return instance.client 42 | } 43 | 44 | func (p *SpicedbPlugin) Start(ctx context.Context) error { 45 | 46 | grpcSecurity, err := grpcutil.WithSystemCerts(grpcutil.VerifyCA) 47 | if p.config.Insecure { 48 | grpcSecurity = grpc.WithInsecure() 49 | } 50 | 51 | if p.config.Schemaprefix != "" { 52 | Schemaprefix = p.config.Schemaprefix 53 | } 54 | 55 | client, err := authzed.NewClient( 56 | p.config.Endpoint, 57 | // grpcutil.WithSystemCerts(grpcutil.VerifyCA), 58 | grpcSecurity, 59 | grpcutil.WithInsecureBearerToken(p.config.Token), 60 | ) 61 | 62 | p.client = client 63 | 64 | // Expose plugin instance in global to be able to access the authzed client from the custom builtins 65 | instance = p 66 | 67 | p.manager.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateOK}) 68 | 69 | return err 70 | 71 | } 72 | 73 | func (p *SpicedbPlugin) Stop(ctx context.Context) { 74 | p.manager.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateNotReady}) 75 | } 76 | 77 | func (p *SpicedbPlugin) Reconfigure(ctx context.Context, config any) { 78 | // Todo: Lock schemaprefix 79 | p.mtx.Lock() 80 | defer p.mtx.Unlock() 81 | 82 | if p.config.Endpoint != config.(Config).Endpoint { 83 | p.Stop(ctx) 84 | if err := p.Start(ctx); err != nil { 85 | p.manager.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateErr}) 86 | } 87 | } 88 | p.config = config.(Config) 89 | } 90 | 91 | type Factory struct{} 92 | 93 | func (Factory) New(m *plugins.Manager, config any) plugins.Plugin { 94 | 95 | m.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateNotReady}) 96 | 97 | return &SpicedbPlugin{ 98 | manager: m, 99 | config: config.(Config), 100 | } 101 | } 102 | 103 | func (Factory) Validate(_ *plugins.Manager, config []byte) (any, error) { 104 | parsedConfig := Config{} 105 | err := util.Unmarshal(config, &parsedConfig) 106 | return parsedConfig, err 107 | } 108 | --------------------------------------------------------------------------------