├── .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 |
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 |
--------------------------------------------------------------------------------