Files
releases/v1/server/server.go
T
dependabot[bot] 0911080ea6 build(deps): bump the dependencies group across 2 directories with 12 updates (#8674)
Bumps the dependencies group with 8 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) |
`1.9.0` | `1.10.1` |
|
[github.com/huandu/go-sqlbuilder](https://github.com/huandu/go-sqlbuilder)
| `1.40.2` | `1.41.0` |
| [github.com/lestrrat-go/jwx/v3](https://github.com/lestrrat-go/jwx) |
`3.1.0` | `3.1.1` |
|
[github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter)
| `1.1.0` | `1.1.4` |
| [github.com/vektah/gqlparser/v2](https://github.com/vektah/gqlparser)
| `2.5.32` | `2.5.33` |
| [golang.org/x/net](https://github.com/golang/net) | `0.53.0` |
`0.54.0` |
| [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.80.0` |
`1.81.0` |
| gopkg.in/ini.v1 | `1.67.1` | `1.67.2` |

Bumps the dependencies group with 11 updates in the /e2e directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify) |
`1.9.0` | `1.10.1` |
|
[github.com/huandu/go-sqlbuilder](https://github.com/huandu/go-sqlbuilder)
| `1.40.2` | `1.41.0` |
| [github.com/lestrrat-go/jwx/v3](https://github.com/lestrrat-go/jwx) |
`3.1.0` | `3.1.1` |
|
[github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter)
| `1.1.0` | `1.1.4` |
| [github.com/vektah/gqlparser/v2](https://github.com/vektah/gqlparser)
| `2.5.32` | `2.5.33` |
| [golang.org/x/net](https://github.com/golang/net) | `0.53.0` |
`0.54.0` |
| [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.80.0` |
`1.81.0` |
| gopkg.in/ini.v1 | `1.67.1` | `1.67.2` |
|
[github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql)
| `1.9.3` | `1.10.0` |
|
[github.com/microsoft/go-mssqldb](https://github.com/microsoft/go-mssqldb)
| `1.9.8` | `1.10.0` |
| [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) | `1.49.1` |
`1.50.1` |


Updates `github.com/fsnotify/fsnotify` from 1.9.0 to 1.10.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fsnotify/fsnotify/releases">github.com/fsnotify/fsnotify's
releases</a>.</em></p>
<blockquote>
<h2>v1.10.1</h2>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</p>
</li>
<li>
<p>inotify, windows: don't rename sibling watches sharing a path prefix
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/754">fsnotify/fsnotify#754</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/755">fsnotify/fsnotify#755</a></p>
<h2>v1.10.0</h2>
<p>This version of fsnotify needs Go 1.23.</p>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: improve initialization error message (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>)</p>
</li>
<li>
<p>inotify: send Rename event if recursive watch is renamed (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>)</p>
</li>
<li>
<p>inotify: avoid copying event buffers when reading names (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>)</p>
</li>
<li>
<p>kqueue: skip dangling symlinks (ENOENT) in watchDirectoryFiles, so a
bad entry no longer aborts Watcher.Add for the whole directory (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</p>
</li>
<li>
<p>kqueue: drop watches directly in Close() to fix a file descriptor
leak when recycling watchers (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>)</p>
</li>
<li>
<p>windows: fix nil pointer dereference in remWatch (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>)</p>
</li>
<li>
<p>windows: lock watch field updates against concurrent WatchList to fix
a race introduced in v1.9.0 (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>,
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/696">fsnotify/fsnotify#696</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/709">fsnotify/fsnotify#709</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/731">fsnotify/fsnotify#731</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/736">fsnotify/fsnotify#736</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/740">fsnotify/fsnotify#740</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/741">fsnotify/fsnotify#741</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/748">fsnotify/fsnotify#748</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/749">fsnotify/fsnotify#749</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md">github.com/fsnotify/fsnotify's
changelog</a>.</em></p>
<blockquote>
<h2>1.10.1 2026-05-04</h2>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</p>
</li>
<li>
<p>inotify, windows: don't rename sibling watches sharing a path prefix
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/754">fsnotify/fsnotify#754</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/755">fsnotify/fsnotify#755</a></p>
<h2>1.10.0 2026-04-30</h2>
<p>This version of fsnotify needs Go 1.23.</p>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: improve initialization error message (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>)</p>
</li>
<li>
<p>inotify: send Rename event if recursive watch is renamed (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>)</p>
</li>
<li>
<p>inotify: avoid copying event buffers when reading names (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>)</p>
</li>
<li>
<p>kqueue: skip dangling symlinks (ENOENT) in watchDirectoryFiles, so a
bad entry no longer aborts Watcher.Add for the whole directory (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</p>
</li>
<li>
<p>kqueue: drop watches directly in Close() to fix a file descriptor
leak
when recycling watchers (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>)</p>
</li>
<li>
<p>windows: fix nil pointer dereference in remWatch (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>)</p>
</li>
<li>
<p>windows: lock watch field updates against concurrent WatchList to fix
a race introduced in v1.9.0 (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>,
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/696">fsnotify/fsnotify#696</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/709">fsnotify/fsnotify#709</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/731">fsnotify/fsnotify#731</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/736">fsnotify/fsnotify#736</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/740">fsnotify/fsnotify#740</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/741">fsnotify/fsnotify#741</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/748">fsnotify/fsnotify#748</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/749">fsnotify/fsnotify#749</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/76b01a6e8f502187fecedea8b025e79e5a86085c"><code>76b01a6</code></a>
Release 1.10.1</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/fec150b807510e54e5b25def4b6e5fb001b4898c"><code>fec150b</code></a>
Update changelog</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/162b4216ab8f92ecd26425530bee198972c9b3cb"><code>162b421</code></a>
inotify, windows: don't rename sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/224257f23b2f3a96509b316c5cead71dd4a9099a"><code>224257f</code></a>
inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/e0c956c0ccaf51562fee30ef5c055c74e6ae2104"><code>e0c956c</code></a>
windows: document directory Write events and stabilize tests (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/745">#745</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/8d01d7b9cbe0199e4a1e60fbd965fb05dbb42123"><code>8d01d7b</code></a>
Release 1.10.0</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/602284e4a8cadd488d7a5fa07c48462dfac25108"><code>602284e</code></a>
Update changelog</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/7f03e59f9659552d8a084e03024cb9b983748ed7"><code>7f03e59</code></a>
kqueue: skip ENOENT entries in watchDirectoryFiles (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/dab9dde2fc9ba4d0c1076318f81cabcc8fdb2ec9"><code>dab9dde</code></a>
windows: lock watch field updates against concurrent WatchList (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>)
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/eadf267ce152b5e62d48cc2c13bb08bd4062b6c7"><code>eadf267</code></a>
kqueue: drop watches directly in Close() instead of going through
remove() (#...</li>
<li>Additional commits viewable in <a
href="https://github.com/fsnotify/fsnotify/compare/v1.9.0...v1.10.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/huandu/go-sqlbuilder` from 1.40.2 to 1.41.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/huandu/go-sqlbuilder/releases">github.com/huandu/go-sqlbuilder's
releases</a>.</em></p>
<blockquote>
<h2>v1.41.0</h2>
<ul>
<li><code>[NEW]</code> Add an option <code>NoExpand</code> to change
default field &quot;expand&quot; behavior for backward compatibility.
See <a
href="https://redirect.github.com/huandu/go-sqlbuilder/issues/237">#237</a>
for details.</li>
</ul>
<p>NOTE: Starting from v1.40.0, the <code>Struct</code> utility type
defaults to expanding non-primitive struct fields. This change caused
unexpected behavior for some users. To address this without reverting
the features introduced in v1.40.0, we have introduced a <code>NoExpand
</code>configuration. When set to true, all fields will default to no
expansion (preserving legacy behavior). You can still opt-in to
expansion for specific fields by using the
<code>fieldopt:&quot;expand&quot;</code> tag.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/huandu/go-sqlbuilder/compare/v1.40.0...v1.41.0">https://github.com/huandu/go-sqlbuilder/compare/v1.40.0...v1.41.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/huandu/go-sqlbuilder/commit/44837218602c8f96eac9f27411d5b93a20dc979d"><code>4483721</code></a>
fix <a
href="https://redirect.github.com/huandu/go-sqlbuilder/issues/237">#237</a>:
add option <code>NoExpand</code> to control default field
&quot;expand&quot; behavior</li>
<li>See full diff in <a
href="https://github.com/huandu/go-sqlbuilder/compare/v1.40.2...v1.41.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/lestrrat-go/jwx/v3` from 3.1.0 to 3.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/lestrrat-go/jwx/releases">github.com/lestrrat-go/jwx/v3's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.1</h2>
<p>For more detailed release notes, see <a
href="https://github.com/lestrrat-go/jwx/blob/v3.1.1/Changes">Changes</a>.</p>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump pozil/auto-assign-issue from 2.2.0 to 2.2.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2045">lestrrat-go/jwx#2045</a></li>
<li>guard ecdsa coordinates against oversized big.Int by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2050">lestrrat-go/jwx#2050</a></li>
<li>reject jwe with conflicting alg in protected vs per-recipient by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2052">lestrrat-go/jwx#2052</a></li>
<li>fix AddressClaim.MarshalJSON for non-printable bytes by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2056">lestrrat-go/jwx#2056</a></li>
<li>jwt: only call ParseForm when WithFormKey is supplied by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2058">lestrrat-go/jwx#2058</a></li>
<li>jws: jkuProvider rejects fetched keys marked use=enc by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2060">lestrrat-go/jwx#2060</a></li>
<li>jwa: unify SignatureAlgorithm/KeyEncryption/ContentEncryption into
one registry by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2066">lestrrat-go/jwx#2066</a></li>
<li>build(deps): bump pozil/auto-assign-issue from
f245a9119ba5cc2fed4aa7b8268d576d40acddf0 to
7bf9d82c77d45976224660b873fc83e60576c5aa by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2065">lestrrat-go/jwx#2065</a></li>
<li>cmd/jwx: warn on private-key-to-tty + reject keysize&lt;=0 for oct
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2071">lestrrat-go/jwx#2071</a></li>
<li>jws: refuse &quot;b64&quot; header in VerifyCompactFast by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2081">lestrrat-go/jwx#2081</a></li>
<li>jws: VerifyCompactFast refusals match jws.VerifyError() class by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2083">lestrrat-go/jwx#2083</a></li>
<li>jws: name loose keySet options in fan-out verify error by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2085">lestrrat-go/jwx#2085</a></li>
<li>jws: honor RFC 7797 b64=false in Message.MarshalJSON by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2087">lestrrat-go/jwx#2087</a></li>
<li>jws: reject literal-JSON &quot;protected&quot; in general-form JWS
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2089">lestrrat-go/jwx#2089</a></li>
<li>jwt: ParseRequest: don't skip form body on chunked transfer by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2091">lestrrat-go/jwx#2091</a></li>
<li>jwt: pedantic mode enforces cty=JWT nested-envelope shape by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2094">lestrrat-go/jwx#2094</a></li>
<li>jwt: defensively reject missing claims in MaxDeltaIs / MinDeltaIs by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2099">lestrrat-go/jwx#2099</a></li>
<li>jwt: ParseInsecure: parse loop-local payload, not original input by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2097">lestrrat-go/jwx#2097</a></li>
<li>jws: Verify rejects b64=false without &quot;b64&quot; listed in
&quot;crit&quot; by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2102">lestrrat-go/jwx#2102</a></li>
<li>jws: Sign auto-declares &quot;b64&quot; in &quot;crit&quot; when
emitting b64=false by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2104">lestrrat-go/jwx#2104</a></li>
<li>jws: declare &quot;b64&quot; as typed bool header field by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2106">lestrrat-go/jwx#2106</a></li>
<li>jws: reject general-form JWS with top-level &quot;header&quot;
sibling of &quot;signatures&quot; by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2108">lestrrat-go/jwx#2108</a></li>
<li>jws: typed sentinel for AlgorithmsForKey unclassifiable-key failures
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2110">lestrrat-go/jwx#2110</a></li>
<li>jws: VerifyMessage observes ctx cancellation between loop iterations
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2112">lestrrat-go/jwx#2112</a></li>
<li>jws: cleanup follow-ups from recent review (low-severity batch) by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2114">lestrrat-go/jwx#2114</a></li>
<li>jwe: DecryptMessage observes ctx cancellation between loop
iterations by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2117">lestrrat-go/jwx#2117</a></li>
<li>jwe: parse and bound-check PBES2 p2c in int64 space; name the
violated bound by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2119">lestrrat-go/jwx#2119</a></li>
<li>jwe: WithKey validates alg-vs-key shape at option-time by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2121">lestrrat-go/jwx#2121</a></li>
<li>jwe: compression cap error names &quot;decompressed&quot; payload,
the option, and the size by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2123">lestrrat-go/jwx#2123</a></li>
<li>jwe: bound joined-error count and drop redundant outer Decrypt
prefix by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2125">lestrrat-go/jwx#2125</a></li>
<li>jwe: keySetProvider surfaces per-key errors via errors.Join by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2127">lestrrat-go/jwx#2127</a></li>
<li>jwe: add WithDisabledKeyAlgorithms global policy hook by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2129">lestrrat-go/jwx#2129</a></li>
<li>jwe: document WithMaxDecompressBufferSize behavior at non-positive
values by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2131">lestrrat-go/jwx#2131</a></li>
<li>jwk: stop duplicating JWK fields at JWKS top level on parse by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2133">lestrrat-go/jwx#2133</a></li>
<li>jwk: wrap ParseKey errors with ParseError sentinel by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2135">lestrrat-go/jwx#2135</a></li>
<li>jwk: stream the keys array with cap-before-allocate by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2137">lestrrat-go/jwx#2137</a></li>
<li>jwk: treat nil key from custom KeyParser as continue, not success by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2140">lestrrat-go/jwx#2140</a></li>
<li>jwk: fix phantom ContinueParseError refs and unmarshaler typo in
docs by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2142">lestrrat-go/jwx#2142</a></li>
<li>Changes: draft v3.1.1 release notes by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2155">lestrrat-go/jwx#2155</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/lestrrat-go/jwx/compare/v3.1.0...v3.1.1">https://github.com/lestrrat-go/jwx/compare/v3.1.0...v3.1.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/lestrrat-go/jwx/blob/v3.1.1/Changes">github.com/lestrrat-go/jwx/v3's
changelog</a>.</em></p>
<blockquote>
<p>v3.1.1 7 May 2026</p>
<ul>
<li>
<p>[jws] Coordinated RFC 7797 <code>b64=false</code> handling pass:
<code>jws.Verify</code>
rejects payloads with <code>b64=false</code> unless <code>b64</code> is
also listed in
<code>crit</code>; <code>jws.Sign</code> auto-declares <code>b64</code>
in <code>crit</code> when emitting
<code>b64=false</code>; <code>Message.MarshalJSON</code> honors
<code>b64=false</code> instead of
silently re-encoding; <code>jws.VerifyCompactFast</code> refuses any
compact
JWS carrying <code>b64</code> (the fast path doesn't process extension
headers); and <code>b64</code> is now declared as a typed boolean header
field rather than handled ad-hoc.
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2081">#2081</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2087">#2087</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2102">#2102</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2104">#2104</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2106">#2106</a>)</p>
</li>
<li>
<p>[jws] Reject malformed general-form JSON-serialized JWS: inputs
with a top-level <code>header</code> member as a sibling of
<code>signatures</code> are
rejected (the spec only permits <code>header</code> inside per-signature
objects), as are inputs whose <code>protected</code> member is a literal
JSON object instead of a base64url-encoded string.
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2089">#2089</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2108">#2108</a>)</p>
</li>
<li>
<p>[jws] <code>jws.AlgorithmsForKey</code> failures from unclassifiable
keys
are now wrapped in a typed sentinel so callers can branch on
&quot;couldn't categorize this key&quot; without string matching the
error
message. (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2110">#2110</a>)</p>
</li>
<li>
<p>[jws] Verify error-shape consistency: <code>VerifyCompactFast</code>
refusals now match the <code>jws.VerifyError()</code> taxonomy used by
the
slow path, fan-out verify errors name the loose <code>WithKeySet</code>
options that were tried, multi-signature <code>b64</code> mismatches
name
the offending signature index and conflicting value, and the
compact <code>b64=false</code>+payload-contains-<code>.</code> error
references RFC
7797 §5.2 and points at <code>WithDetachedPayload</code>.
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2083">#2083</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2085">#2085</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2114">#2114</a>)</p>
</li>
<li>
<p>[jws] Keys fetched via the <code>jku</code> header are no longer
accepted
for signature verification when the JWK declares <code>use=enc</code>.
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2060">#2060</a>)</p>
</li>
<li>
<p>[jws][jwe] <code>jws.VerifyMessage</code> and
<code>jwe.DecryptMessage</code> observe
context cancellation between loop iterations rather than only at
boundaries. Long fan-out verify/decrypt loops now respond to a
cancelled context promptly. (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2112">#2112</a>,
<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2117">#2117</a>)</p>
</li>
<li>
<p>[jwe] Reject PBES2 messages whose <code>p2c</code> (iteration count)
does
not parse cleanly into int64 or violates the configured bound.
The error now names the violated bound (min vs max) instead of
the generic &quot;out of range&quot;. (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2119">#2119</a>)</p>
</li>
<li>
<p>[jwe] <code>jwe.WithKey()</code> validates the alg-vs-key shape at
option
construction time rather than during encryption, so misuse
surfaces at the call site instead of inside the encrypt loop.
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2121">#2121</a>)</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/59b8b1b4239be0b470e5d939f16759793bc4a203"><code>59b8b1b</code></a>
release v3.1.1</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/4d4ab015bec294bcee30d83506def12906e9da2c"><code>4d4ab01</code></a>
Changes: draft v3.1.1 release notes (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2155">#2155</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/ad739f565db19d9d8fe805eb5db0b05d5441f971"><code>ad739f5</code></a>
jwk: fix phantom ContinueParseError refs and unmarshaler typo in docs
(<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2142">#2142</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/3227cf9c3d8ddc2d9728d58db03f905b61972f69"><code>3227cf9</code></a>
jwk: treat nil key from custom KeyParser as continue, not success (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2140">#2140</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/82c067ed57cbcbf0aba072518ed928c96306e951"><code>82c067e</code></a>
jwk: stream the keys array with cap-before-allocate (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2137">#2137</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/931a815632dab9bb0e49c81aa701dbf9e9a77f04"><code>931a815</code></a>
jwk: wrap ParseKey errors with ParseError sentinel (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2135">#2135</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/53f62259012b1d934a1dc8c07ebbd8f516f7b919"><code>53f6225</code></a>
jwk: stop duplicating JWK fields at JWKS top level on parse (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2133">#2133</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/8943519997fa2fe4e7e0423baf9cd8e9bb45ddd9"><code>8943519</code></a>
jwe: document WithMaxDecompressBufferSize behavior at non-positive
values (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2">#2</a>...</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/4797307a63aa0dd6847ee64cbdacbb878226b4f8"><code>4797307</code></a>
jwe: add WithDisabledKeyAlgorithms global policy hook (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2129">#2129</a>)</li>
<li><a
href="https://github.com/lestrrat-go/jwx/commit/de41d0ef9e0b8f6a9f844f5313abbc510dda78fb"><code>de41d0e</code></a>
jwe: keySetProvider surfaces per-key errors via errors.Join (<a
href="https://redirect.github.com/lestrrat-go/jwx/issues/2127">#2127</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/lestrrat-go/jwx/compare/v3.1.0...v3.1.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/olekukonko/tablewriter` from 1.1.0 to 1.1.4
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/a0dea8a90a8a0c7610afb5588d2f15a57f4aa9a2"><code>a0dea8a</code></a>
no need to disable twice</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/a4fb40afbe367fd0733ce7b45223034febf7b0b4"><code>a4fb40a</code></a>
Merge pull request <a
href="https://redirect.github.com/olekukonko/tablewriter/issues/314">#314</a>
from sducamp/fix/rendition-debug-leak</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/6bc4cb4866ab2a10340bf0d11c41e676b546e253"><code>6bc4cb4</code></a>
fix: prevent debug output leak from renderer during Options()
reconfiguration</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/54408fee90b7a66a94d9d71f789d42e03f45109b"><code>54408fe</code></a>
update ll to v0.1.6</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/5ea5f3c761e556def568d7e07df774c55ae66071"><code>5ea5f3c</code></a>
add mote tab test ans update go mod</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/1455dd8dd79719f142013f59e300fcdf0144f3fd"><code>1455dd8</code></a>
Merge pull request <a
href="https://redirect.github.com/olekukonko/tablewriter/issues/311">#311</a>
from olekukonko/tabber</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/d7b0a55c1f9c6bd55eceaa22dfb0123bac23f281"><code>d7b0a55</code></a>
improve tab and make test more predictable</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/62117a2ca655057ba2e61f2d18896f619fc48230"><code>62117a2</code></a>
add space default <a
href="https://redirect.github.com/olekukonko/tablewriter/issues/312">#312</a>
for colorized renderer</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/4958831ad1de62ec94567bf5d42a8a9b2c50e74d"><code>4958831</code></a>
ll v0.1.5 update enables logging by default hence disable</li>
<li><a
href="https://github.com/olekukonko/tablewriter/commit/1c68e06c65b87d5416aada2737b6683fadd1b25b"><code>1c68e06</code></a>
use space for padding as default <a
href="https://redirect.github.com/olekukonko/tablewriter/issues/312">#312</a></li>
<li>Additional commits viewable in <a
href="https://github.com/olekukonko/tablewriter/compare/v1.1.0...v1.1.4">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/vektah/gqlparser/v2` from 2.5.32 to 2.5.33
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vektah/gqlparser/releases">github.com/vektah/gqlparser/v2's
releases</a>.</em></p>
<blockquote>
<h2>v2.5.33</h2>
<h2>What's Changed</h2>
<ul>
<li>fix: allow repeatable directives on GraphQL document by <a
href="https://github.com/fredzqm"><code>@​fredzqm</code></a> in <a
href="https://redirect.github.com/vektah/gqlparser/pull/418">vektah/gqlparser#418</a></li>
<li>feat: create a new ScalarLeafsRuleWithoutSuggestions validator rule
by <a
href="https://github.com/XuankangLin"><code>@​XuankangLin</code></a> in
<a
href="https://redirect.github.com/vektah/gqlparser/pull/413">vektah/gqlparser#413</a></li>
<li>refactor: format lines in scalar_leafs.go by <a
href="https://github.com/XuankangLin"><code>@​XuankangLin</code></a> in
<a
href="https://redirect.github.com/vektah/gqlparser/pull/423">vektah/gqlparser#423</a></li>
<li>Fix negative Position.Column for definitions with block string
descriptions (<a
href="https://redirect.github.com/vektah/gqlparser/issues/254">#254</a>)
by <a href="https://github.com/riwal42c"><code>@​riwal42c</code></a> in
<a
href="https://redirect.github.com/vektah/gqlparser/pull/422">vektah/gqlparser#422</a></li>
<li>Update spec to 2023 version by <a
href="https://github.com/StevenACoffman"><code>@​StevenACoffman</code></a>
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/401">vektah/gqlparser#401</a></li>
<li>Bump picomatch from 2.3.1 to 2.3.2 in /validator/imported by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/420">vektah/gqlparser#420</a></li>
<li>Bump <code>@​babel/preset-env</code> from 7.29.0 to 7.29.2 in
/validator/imported in the actions-deps group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/419">vektah/gqlparser#419</a></li>
<li>Bump brace-expansion from 1.1.12 to 1.1.13 in /validator/imported by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/421">vektah/gqlparser#421</a></li>
<li>Bump prettier from 3.8.1 to 3.8.2 in /validator/imported in the
actions-deps group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/424">vektah/gqlparser#424</a></li>
<li>Bump prettier from 3.8.2 to 3.8.3 in /validator/imported in the
actions-deps group by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/425">vektah/gqlparser#425</a></li>
<li>Bump minimatch from 3.0.4 to 3.1.5 in /validator/imported by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/vektah/gqlparser/pull/417">vektah/gqlparser#417</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/XuankangLin"><code>@​XuankangLin</code></a>
made their first contribution in <a
href="https://redirect.github.com/vektah/gqlparser/pull/413">vektah/gqlparser#413</a></li>
<li><a href="https://github.com/riwal42c"><code>@​riwal42c</code></a>
made their first contribution in <a
href="https://redirect.github.com/vektah/gqlparser/pull/422">vektah/gqlparser#422</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/vektah/gqlparser/compare/v2.5.32...v2.5.33">https://github.com/vektah/gqlparser/compare/v2.5.32...v2.5.33</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/vektah/gqlparser/commit/41b7913b390ac4278ca7fc766afd586c1e6df819"><code>41b7913</code></a>
Bump minimatch from 3.0.4 to 3.1.5 in /validator/imported (<a
href="https://redirect.github.com/vektah/gqlparser/issues/417">#417</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/3c33bbeb81dd06c1dc5d5e56c97c2ac74e3a5e71"><code>3c33bbe</code></a>
Bump prettier in /validator/imported in the actions-deps group (<a
href="https://redirect.github.com/vektah/gqlparser/issues/425">#425</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/f63b51fcf337b673ac06b593262f7ed72d11de24"><code>f63b51f</code></a>
Update spec to 2023 version (<a
href="https://redirect.github.com/vektah/gqlparser/issues/401">#401</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/1f1383cb52d49c63919a791309a2c80e16a83c6f"><code>1f1383c</code></a>
Bump prettier in /validator/imported in the actions-deps group (<a
href="https://redirect.github.com/vektah/gqlparser/issues/424">#424</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/021901042dbe56648020832ee88973acd389ea64"><code>0219010</code></a>
Fix negative Position.Column for definitions with block string
descriptions (...</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/16bf3c4da15f2de0a8648e66f956ef9da2129d8e"><code>16bf3c4</code></a>
refactor: format lines in scalar_leafs.go (<a
href="https://redirect.github.com/vektah/gqlparser/issues/423">#423</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/be677085400ffa5fd6e0ac86d98d1cf94db05efe"><code>be67708</code></a>
Bump brace-expansion from 1.1.12 to 1.1.13 in /validator/imported (<a
href="https://redirect.github.com/vektah/gqlparser/issues/421">#421</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/61efb18da16d7f45f8050ecb576d27b85e6861da"><code>61efb18</code></a>
feat: create a new ScalarLeafsRuleWithoutSuggestions validator rule (<a
href="https://redirect.github.com/vektah/gqlparser/issues/413">#413</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/a9bb4644d5911effae909bab06029df7f11c402c"><code>a9bb464</code></a>
feat(validator): handle repeatable directives correctly (<a
href="https://redirect.github.com/vektah/gqlparser/issues/418">#418</a>)</li>
<li><a
href="https://github.com/vektah/gqlparser/commit/b239ec3bfe15a44330f9752bd7fb56cc41cf1c2b"><code>b239ec3</code></a>
Bump <code>@​babel/preset-env</code> in /validator/imported in the
actions-deps group (<a
href="https://redirect.github.com/vektah/gqlparser/issues/419">#419</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/vektah/gqlparser/compare/v2.5.32...v2.5.33">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/net` from 0.53.0 to 0.54.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/net/commit/b138e06246cb323f2f380c2b7f7dd91f581dd56b"><code>b138e06</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/net/commit/689f70a42abd350f3a1aaa70b0d13eb9543d927a"><code>689f70a</code></a>
quic: fix wrong final size being used for RESET_STREAM frame</li>
<li><a
href="https://github.com/golang/net/commit/208f306b2f0fd008b388bee2c2644be279778e94"><code>208f306</code></a>
http3: increase handshake timeout</li>
<li><a
href="https://github.com/golang/net/commit/49810da71b9026da9e0d028a6ad8c7730c52d9c4"><code>49810da</code></a>
http2: enable net/http wrapping when go &gt;= 1.27</li>
<li><a
href="https://github.com/golang/net/commit/5e11a5ab891c117eda83b4304d60dd13286c1c76"><code>5e11a5a</code></a>
quic: fix data race in streamForFrame</li>
<li><a
href="https://github.com/golang/net/commit/8c63081cd380ea768db5651941614b73472160ff"><code>8c63081</code></a>
http2: use empty Transport rather than DefaultTransport in
http2wrap</li>
<li><a
href="https://github.com/golang/net/commit/fc7b466ca49cb204039630533ece4fc557eb35cd"><code>fc7b466</code></a>
http2: add http2wrap test</li>
<li><a
href="https://github.com/golang/net/commit/15c2cb1875fd727313dc4de909b3ee149422fbe2"><code>15c2cb1</code></a>
http2: avoid overflowing 32-bit int when http2wrap enabled</li>
<li><a
href="https://github.com/golang/net/commit/64651885c2f2d745d77af2d7af2edbf568c179af"><code>6465188</code></a>
http2: add wrapped Server</li>
<li><a
href="https://github.com/golang/net/commit/72f419a894cb0597dd5b6bcf119086bf2af41231"><code>72f419a</code></a>
http2: add wrapped ClientConn</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/net/compare/v0.53.0...v0.54.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golang.org/x/text` from 0.36.0 to 0.37.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/text/commit/3ef517e623a4bfc08d6457f87d73afda7af7d8e1"><code>3ef517e</code></a>
go.mod: update golang.org/x dependencies</li>
<li>See full diff in <a
href="https://github.com/golang/text/compare/v0.36.0...v0.37.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `google.golang.org/grpc` from 1.80.0 to 1.81.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/grpc/grpc-go/releases">google.golang.org/grpc's
releases</a>.</em></p>
<blockquote>
<h2>Release 1.81.0</h2>
<h1>Behavior Changes</h1>
<ul>
<li>balancer/rls: Switch gauge metrics to asynchronous emission (once
per collection cycle) to reduce telemetry noise and align with other
gRPC language implementations. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8808">#8808</a>)</li>
</ul>
<h1>Dependencies</h1>
<ul>
<li>Minimum supported Go version is now 1.25. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8969">#8969</a>)</li>
</ul>
<h1>Bug Fixes</h1>
<ul>
<li>xds: Use the leaf cluster's security config for the TLS handshake
instead of the aggregate cluster's config. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8956">#8956</a>)</li>
<li>transport: Send a <code>RST_STREAM</code> when receiving an
<code>END_STREAM</code> when the stream is not already half-closed. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8832">#8832</a>)</li>
<li>xds: Fix ADS resource name validation to prevent a panic. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8970">#8970</a>)</li>
</ul>
<h1>New Features</h1>
<ul>
<li>grpc/stats: Add support for custom labels in per-call metrics (<a
href="https://github.com/grpc/proposal/blob/master/A108-otel-custom-per-call-label.md">gRFC
A108</a>). (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9008">#9008</a>)</li>
<li>xds: Add support for Server Name Indication (SNI) and SAN validation
(<a
href="https://github.com/grpc/proposal/blob/master/A101-SNI-setting-and-SNI-SAN-validation.md">gRFC
A101</a>). Disabled by default. To enable, set
<code>GRPC_EXPERIMENTAL_XDS_SNI=true</code> environment variable. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9016">#9016</a>)</li>
<li>xds: Add support to control which fields get propagated from ORCA
backend metric reports to LRS load reports (<a
href="https://github.com/grpc/proposal/blob/master/A85-lrs-custom-metrics-changes.md">gRFC
A85</a>). Disabled by default. To enable, set
<code>GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION=true</code>. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9005">#9005</a>)</li>
<li>xds: Add metrics to track xDS client connectivity and cached
resource state (<a
href="https://github.com/grpc/proposal/blob/master/A78-grpc-metrics-wrr-pf-xds.md">gRFC
A78</a>). (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8807">#8807</a>)</li>
<li>stats/otel: Enhance <code>grpc.subchannel.disconnections</code>
metric by adding disconnection reason to the
<code>grpc.disconnect_error</code> label (<a
href="https://github.com/grpc/proposal/blob/master/A94-subchannel-otel-metrics.md">gRFC
A94</a>). This provides granular insights into why subchannels are
closing. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8973">#8973</a>)</li>
<li>mem: Add <code>mem.Buffer.Slice()</code> API to slice the buffer
like a slice. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8977">#8977</a>)
<ul>
<li>Special Thanks: <a
href="https://github.com/ash2k"><code>@​ash2k</code></a></li>
</ul>
</li>
</ul>
<h1>Performance Improvements</h1>
<ul>
<li>alts: Pool read buffers to lower memory utilization when sockets are
unreadable. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/8964">#8964</a>)</li>
<li>transport: Pool HTTP/2 framer read buffers to reduce idle memory
consumption. Currently limited to Linux for ALTS and non-encrypted
transports (TCP, Unix). To disable, set
<code>GRPC_GO_EXPERIMENTAL_HTTP_FRAMER_READ_BUFFER_POOLING=false</code>
and report any issues. (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9032">#9032</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/grpc/grpc-go/commit/cb18228317ff523e63d931b4058b0329585b7dcd"><code>cb18228</code></a>
Change version to 1.81.0 (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9062">#9062</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/96748f973e20bbfcafa19a8bdffc85ad5da138d1"><code>96748f9</code></a>
Cherry-pick <a
href="https://redirect.github.com/grpc/grpc-go/issues/9105">#9105</a> to
1.81.x (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9106">#9106</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/91832222f0144f76527b630ca55cfea6e1aa015a"><code>9183222</code></a>
Cherry pick <a
href="https://redirect.github.com/grpc/grpc-go/issues/9055">#9055</a>,
<a href="https://redirect.github.com/grpc/grpc-go/issues/9032">#9032</a>
to v1.81.x (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9095">#9095</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/5cba6da4211f3b130238c792937f5921741b616a"><code>5cba6da</code></a>
Revert &quot;deps: update dependencies for all modules (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9065">#9065</a>)&quot;
(<a
href="https://redirect.github.com/grpc/grpc-go/issues/9067">#9067</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/af8a9364aa7523ab24d214e9ef13e6ad64d5c5f9"><code>af8a936</code></a>
deps: update dependencies for all modules (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9065">#9065</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/cdc60dfaaadde45e16aa3c28237c0e655a722c1a"><code>cdc60df</code></a>
transport: optimize heap allocations in ready reader and update syscall
conne...</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/208d053e3204c806ba9e6205c26aa064c8b42852"><code>208d053</code></a>
xds/resolver: pass complete XDSConfig in RPC context for HTTP filters
(gRFC A...</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/50fe1cc7fd78b78ae638ed90ea78514c934167ac"><code>50fe1cc</code></a>
test: Fix flaky test
<code>TestServerStreaming_ClientCallRecvMsgTwice</code> in
`end2end...</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/d574bad188f25ba03d41a506e6f2ef93837ad10b"><code>d574bad</code></a>
build(deps): bump go.opentelemetry.io/otel/sdk from 1.42.0 to 1.43.0 (<a
href="https://redirect.github.com/grpc/grpc-go/issues/9050">#9050</a>)</li>
<li><a
href="https://github.com/grpc/grpc-go/commit/b8bf4d0488a351c563d63797ffba321585d6bb24"><code>b8bf4d0</code></a>
build(deps): bump go.opentelemetry.io/otel/sdk from 1.42.0 to 1.43.0 in
/inte...</li>
<li>Additional commits viewable in <a
href="https://github.com/grpc/grpc-go/compare/v1.80.0...v1.81.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `gopkg.in/ini.v1` from 1.67.1 to 1.67.2

Updates `github.com/fsnotify/fsnotify` from 1.9.0 to 1.10.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fsnotify/fsnotify/releases">github.com/fsnotify/fsnotify's
releases</a>.</em></p>
<blockquote>
<h2>v1.10.1</h2>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</p>
</li>
<li>
<p>inotify, windows: don't rename sibling watches sharing a path prefix
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/754">fsnotify/fsnotify#754</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/755">fsnotify/fsnotify#755</a></p>
<h2>v1.10.0</h2>
<p>This version of fsnotify needs Go 1.23.</p>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: improve initialization error message (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>)</p>
</li>
<li>
<p>inotify: send Rename event if recursive watch is renamed (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>)</p>
</li>
<li>
<p>inotify: avoid copying event buffers when reading names (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>)</p>
</li>
<li>
<p>kqueue: skip dangling symlinks (ENOENT) in watchDirectoryFiles, so a
bad entry no longer aborts Watcher.Add for the whole directory (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</p>
</li>
<li>
<p>kqueue: drop watches directly in Close() to fix a file descriptor
leak when recycling watchers (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>)</p>
</li>
<li>
<p>windows: fix nil pointer dereference in remWatch (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>)</p>
</li>
<li>
<p>windows: lock watch field updates against concurrent WatchList to fix
a race introduced in v1.9.0 (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>,
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/696">fsnotify/fsnotify#696</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/709">fsnotify/fsnotify#709</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/731">fsnotify/fsnotify#731</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/736">fsnotify/fsnotify#736</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/740">fsnotify/fsnotify#740</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/741">fsnotify/fsnotify#741</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/748">fsnotify/fsnotify#748</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/749">fsnotify/fsnotify#749</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/fsnotify/fsnotify/blob/main/CHANGELOG.md">github.com/fsnotify/fsnotify's
changelog</a>.</em></p>
<blockquote>
<h2>1.10.1 2026-05-04</h2>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</p>
</li>
<li>
<p>inotify, windows: don't rename sibling watches sharing a path prefix
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/754">fsnotify/fsnotify#754</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/755">fsnotify/fsnotify#755</a></p>
<h2>1.10.0 2026-04-30</h2>
<p>This version of fsnotify needs Go 1.23.</p>
<h3>Changes and fixes</h3>
<ul>
<li>
<p>inotify: improve initialization error message (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>)</p>
</li>
<li>
<p>inotify: send Rename event if recursive watch is renamed (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>)</p>
</li>
<li>
<p>inotify: avoid copying event buffers when reading names (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>)</p>
</li>
<li>
<p>kqueue: skip dangling symlinks (ENOENT) in watchDirectoryFiles, so a
bad entry no longer aborts Watcher.Add for the whole directory (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</p>
</li>
<li>
<p>kqueue: drop watches directly in Close() to fix a file descriptor
leak
when recycling watchers (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>)</p>
</li>
<li>
<p>windows: fix nil pointer dereference in remWatch (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>)</p>
</li>
<li>
<p>windows: lock watch field updates against concurrent WatchList to fix
a race introduced in v1.9.0 (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>,
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</p>
</li>
</ul>
<p><a
href="https://redirect.github.com/fsnotify/fsnotify/issues/696">#696</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/696">fsnotify/fsnotify#696</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/709">fsnotify/fsnotify#709</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/731">#731</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/731">fsnotify/fsnotify#731</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/736">#736</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/736">fsnotify/fsnotify#736</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/740">#740</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/740">fsnotify/fsnotify#740</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/741">#741</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/741">fsnotify/fsnotify#741</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/748">fsnotify/fsnotify#748</a>
<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>:
<a
href="https://redirect.github.com/fsnotify/fsnotify/pull/749">fsnotify/fsnotify#749</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/76b01a6e8f502187fecedea8b025e79e5a86085c"><code>76b01a6</code></a>
Release 1.10.1</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/fec150b807510e54e5b25def4b6e5fb001b4898c"><code>fec150b</code></a>
Update changelog</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/162b4216ab8f92ecd26425530bee198972c9b3cb"><code>162b421</code></a>
inotify, windows: don't rename sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/755">#755</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/224257f23b2f3a96509b316c5cead71dd4a9099a"><code>224257f</code></a>
inotify: don't remove sibling watches sharing a path prefix (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/754">#754</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/e0c956c0ccaf51562fee30ef5c055c74e6ae2104"><code>e0c956c</code></a>
windows: document directory Write events and stabilize tests (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/745">#745</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/8d01d7b9cbe0199e4a1e60fbd965fb05dbb42123"><code>8d01d7b</code></a>
Release 1.10.0</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/602284e4a8cadd488d7a5fa07c48462dfac25108"><code>602284e</code></a>
Update changelog</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/7f03e59f9659552d8a084e03024cb9b983748ed7"><code>7f03e59</code></a>
kqueue: skip ENOENT entries in watchDirectoryFiles (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/748">#748</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/dab9dde2fc9ba4d0c1076318f81cabcc8fdb2ec9"><code>dab9dde</code></a>
windows: lock watch field updates against concurrent WatchList (<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/709">#709</a>)
(<a
href="https://redirect.github.com/fsnotify/fsnotify/issues/749">#749</a>)</li>
<li><a
href="https://github.com/fsnotify/fsnotify/commit/eadf267ce152b5e62d48cc2c13bb08bd4062b6c7"><code>eadf267</code></a>
kqueue: drop watches directly in Close() instead of going through
remove() (#...</li>
<li>Additional commits viewable in <a
href="https://github.com/fsnotify/fsnotify/compare/v1.9.0...v1.10.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/huandu/go-sqlbuilder` from 1.40.2 to 1.41.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/huandu/go-sqlbuilder/releases">github.com/huandu/go-sqlbuilder's
releases</a>.</em></p>
<blockquote>
<h2>v1.41.0</h2>
<ul>
<li><code>[NEW]</code> Add an option <code>NoExpand</code> to change
default field &quot;expand&quot; behavior for backward compatibility.
See <a
href="https://redirect.github.com/huandu/go-sqlbuilder/issues/237">#237</a>
for details.</li>
</ul>
<p>NOTE: Starting from v1.40.0, the <code>Struct</code> utility type
defaults to expanding non-primitive struct fields. This change caused
unexpected behavior for some users. To address this without reverting
the features introduced in v1.40.0, we have introduced a <code>NoExpand
</code>configuration. When set to true, all fields will default to no
expansion (preserving legacy behavior). You can still opt-in to
expansion for specific fields by using the
<code>fieldopt:&quot;expand&quot;</code> tag.</p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/huandu/go-sqlbuilder/compare/v1.40.0...v1.41.0">https://github.com/huandu/go-sqlbuilder/compare/v1.40.0...v1.41.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/huandu/go-sqlbuilder/commit/44837218602c8f96eac9f27411d5b93a20dc979d"><code>4483721</code></a>
fix <a
href="https://redirect.github.com/huandu/go-sqlbuilder/issues/237">#237</a>:
add option <code>NoExpand</code> to control default field
&quot;expand&quot; behavior</li>
<li>See full diff in <a
href="https://github.com/huandu/go-sqlbuilder/compare/v1.40.2...v1.41.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `github.com/lestrrat-go/jwx/v3` from 3.1.0 to 3.1.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/lestrrat-go/jwx/releases">github.com/lestrrat-go/jwx/v3's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.1</h2>
<p>For more detailed release notes, see <a
href="https://github.com/lestrrat-go/jwx/blob/v3.1.1/Changes">Changes</a>.</p>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump pozil/auto-assign-issue from 2.2.0 to 2.2.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2045">lestrrat-go/jwx#2045</a></li>
<li>guard ecdsa coordinates against oversized big.Int by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2050">lestrrat-go/jwx#2050</a></li>
<li>reject jwe with conflicting alg in protected vs per-recipient by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2052">lestrrat-go/jwx#2052</a></li>
<li>fix AddressClaim.MarshalJSON for non-printable bytes by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2056">lestrrat-go/jwx#2056</a></li>
<li>jwt: only call ParseForm when WithFormKey is supplied by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2058">lestrrat-go/jwx#2058</a></li>
<li>jws: jkuProvider rejects fetched keys marked use=enc by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2060">lestrrat-go/jwx#2060</a></li>
<li>jwa: unify SignatureAlgorithm/KeyEncryption/ContentEncryption into
one registry by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2066">lestrrat-go/jwx#2066</a></li>
<li>build(deps): bump pozil/auto-assign-issue from
f245a9119ba5cc2fed4aa7b8268d576d40acddf0 to
7bf9d82c77d45976224660b873fc83e60576c5aa by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2065">lestrrat-go/jwx#2065</a></li>
<li>cmd/jwx: warn on private-key-to-tty + reject keysize&lt;=0 for oct
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2071">lestrrat-go/jwx#2071</a></li>
<li>jws: refuse &quot;b64&quot; header in VerifyCompactFast by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2081">lestrrat-go/jwx#2081</a></li>
<li>jws: VerifyCompactFast refusals match jws.VerifyError() class by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2083">lestrrat-go/jwx#2083</a></li>
<li>jws: name loose keySet options in fan-out verify error by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2085">lestrrat-go/jwx#2085</a></li>
<li>jws: honor RFC 7797 b64=false in Message.MarshalJSON by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2087">lestrrat-go/jwx#2087</a></li>
<li>jws: reject literal-JSON &quot;protected&quot; in general-form JWS
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2089">lestrrat-go/jwx#2089</a></li>
<li>jwt: ParseRequest: don't skip form body on chunked transfer by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2091">lestrrat-go/jwx#2091</a></li>
<li>jwt: pedantic mode enforces cty=JWT nested-envelope shape by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2094">lestrrat-go/jwx#2094</a></li>
<li>jwt: defensively reject missing claims in MaxDeltaIs / MinDeltaIs by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2099">lestrrat-go/jwx#2099</a></li>
<li>jwt: ParseInsecure: parse loop-local payload, not original input by
<a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2097">lestrrat-go/jwx#2097</a></li>
<li>jws: Verify rejects b64=false without &quot;b64&quot; listed in
&quot;crit&quot; by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2102">lestrrat-go/jwx#2102</a></li>
<li>jws: Sign auto-declares &quot;b64&quot; in &quot;crit&quot; when
emitting b64=false by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2104">lestrrat-go/jwx#2104</a></li>
<li>jws: declare &quot;b64&quot; as typed bool header field by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2106">lestrrat-go/jwx#2106</a></li>
<li>jws: reject general-form JWS with top-level &quot;header&quot;
sibling of &quot;signatures&quot; by <a
href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in <a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2108">lestrrat-go/jwx#2108</a></li>
<li>jws: typed sentinel for AlgorithmsForKey unclassifiable-key failures
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a> in
<a
href="https://redirect.github.com/lestrrat-go/jwx/pull/2110">lestrrat-go/jwx#2110</a></li>
<li>jws: VerifyMessage observes ctx cancellation between loop iterations
by <a href="https://github.com/lestrrat"><code>@​lestrrat</code></a>
i...

_Description has been truncated_

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Johan Fylling <johan.dev@fylling.se>
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Johan Fylling <johan.dev@fylling.se>
Co-authored-by: Stephan Renatus <stephan.renatus@gmail.com>
2026-05-19 10:13:47 +02:00

3225 lines
87 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package server
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"net"
"net/http"
"net/http/pprof"
"net/url"
"os"
"runtime"
"slices"
"strconv"
"strings"
"sync"
"time"
lru "github.com/hashicorp/golang-lru/v2"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/open-policy-agent/opa/internal/json/patch"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/bundle"
"github.com/open-policy-agent/opa/v1/config"
"github.com/open-policy-agent/opa/v1/hooks"
"github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/metrics"
"github.com/open-policy-agent/opa/v1/plugins"
bundlePlugin "github.com/open-policy-agent/opa/v1/plugins/bundle"
serverDecodingPlugin "github.com/open-policy-agent/opa/v1/plugins/server/decoding"
serverEncodingPlugin "github.com/open-policy-agent/opa/v1/plugins/server/encoding"
"github.com/open-policy-agent/opa/v1/plugins/status"
"github.com/open-policy-agent/opa/v1/rego"
"github.com/open-policy-agent/opa/v1/server/authorizer"
"github.com/open-policy-agent/opa/v1/server/handlers"
"github.com/open-policy-agent/opa/v1/server/identifier"
"github.com/open-policy-agent/opa/v1/server/types"
"github.com/open-policy-agent/opa/v1/server/writer"
"github.com/open-policy-agent/opa/v1/storage"
"github.com/open-policy-agent/opa/v1/topdown"
"github.com/open-policy-agent/opa/v1/topdown/builtins"
iCache "github.com/open-policy-agent/opa/v1/topdown/cache"
"github.com/open-policy-agent/opa/v1/topdown/lineage"
"github.com/open-policy-agent/opa/v1/tracing"
"github.com/open-policy-agent/opa/v1/util"
"github.com/open-policy-agent/opa/v1/version"
)
// AuthenticationScheme enumerates the supported authentication schemes. The
// authentication scheme determines how client identities are established.
type AuthenticationScheme int
// Set of supported authentication schemes.
const (
AuthenticationOff AuthenticationScheme = iota
AuthenticationToken
AuthenticationTLS
)
// AuthorizationScheme enumerates the supported authorization schemes. The authorization
// scheme determines how access to OPA is controlled.
type AuthorizationScheme int
// Set of supported authorization schemes.
const (
AuthorizationOff AuthorizationScheme = iota
AuthorizationBasic
)
const (
// Set of handlers for use in the "handler" dimension of the duration metric.
PromHandlerV0Data = "v0/data"
PromHandlerV1Data = "v1/data"
PromHandlerV1Query = "v1/query"
PromHandlerV1Policies = "v1/policies"
PromHandlerV1Compile = "v1/compile"
PromHandlerV1Config = "v1/config"
PromHandlerV1Status = "v1/status"
PromHandlerIndex = "index"
PromHandlerCatch = "catchall"
PromHandlerHealth = "health"
PromHandlerAPIAuthz = "authz"
pqMaxCacheSize = 100
// OpenTelemetry attributes
otelDecisionIDAttr = "opa.decision_id"
)
var (
supportedTLSVersions = []uint16{tls.VersionTLS10, tls.VersionTLS11, tls.VersionTLS12, tls.VersionTLS13}
unsafeBuiltinsMap = map[string]struct{}{ast.HTTPSend.Name: {}}
intermediateResultsEnabled = os.Getenv("OPA_DECISIONS_INTERMEDIATE_RESULTS") != ""
)
type IntermediateResultsContextKey struct{}
// Server represents an instance of OPA running in server mode.
type Server struct {
Handler http.Handler
DiagnosticHandler http.Handler
router *http.ServeMux
addrs []string
diagAddrs []string
h2cEnabled bool
authentication AuthenticationScheme
authorization AuthorizationScheme
cert *tls.Certificate
tlsConfigMtx sync.RWMutex
certFile string
certFileHash []byte
certKeyFile string
certKeyFileHash []byte
certRefresh time.Duration
certPool *x509.CertPool
certPoolFile string
certPoolFileHash []byte
minTLSVersion uint16
mtx sync.RWMutex
partials map[string]rego.PartialResult
preparedEvalQueries *cache
store storage.Store
manager *plugins.Manager
decisionIDFactory func() string
logger func(context.Context, *Info) error
errLimit int
pprofEnabled bool
runtime *ast.Term
httpListeners []httpListener
metrics Metrics
defaultDecisionPath string
interQueryBuiltinCache iCache.InterQueryCache
interQueryBuiltinValueCache iCache.InterQueryValueCache
allPluginsOkOnce bool
distributedTracingOpts tracing.Options
ndbCacheEnabled bool
unixSocketPerm *string
cipherSuites *[]uint16
hooks hooks.Hooks
compileUnknownsCache *lru.Cache[string, []ast.Ref]
compileMaskingRulesCache *lru.Cache[string, ast.Ref]
}
// Metrics defines the interface that the server requires for recording HTTP
// handler metrics.
type Metrics interface {
RegisterEndpoints(registrar func(path, method string, handler http.Handler))
InstrumentHandler(handler http.Handler, label string) http.Handler
}
// TLSConfig represents the TLS configuration for the server.
// This configuration is used to configure file watchers to reload each file as it
// changes on disk.
type TLSConfig struct {
// CertFile is the path to the server's serving certificate file.
CertFile string
// KeyFile is the path to the server's key file, completing the key pair for the
// CertFile certificate.
KeyFile string
// CertPoolFile is the path to the CA cert pool file. The contents of this file will be
// reloaded when the file changes on disk and used in as trusted client CAs in the TLS config
// for new connections to the server.
CertPoolFile string
}
// Loop will contain all the calls from the server that we'll be listening on.
type Loop func() error
// New returns a new Server.
func New() *Server {
s := Server{}
s.compileUnknownsCache, _ = lru.New[string, []ast.Ref](unknownsCacheSize)
s.compileMaskingRulesCache, _ = lru.New[string, ast.Ref](maskingRuleCacheSize)
return &s
}
// Init initializes the server. This function MUST be called before starting any loops
// from s.Listeners().
func (s *Server) Init(ctx context.Context) (*Server, error) {
s.initRouters(ctx)
var err error
s.hooks.Each(func(h hooks.Hook) {
switch h := h.(type) {
case hooks.InterQueryCacheHook:
if e := h.OnInterQueryCache(ctx, s.interQueryBuiltinCache); e != nil {
err = errors.Join(err, e)
}
case hooks.InterQueryValueCacheHook:
if e := h.OnInterQueryValueCache(ctx, s.interQueryBuiltinValueCache); e != nil {
err = errors.Join(err, e)
}
}
})
if err != nil {
return nil, err
}
txn, err := s.store.NewTransaction(ctx, storage.WriteParams)
if err != nil {
return nil, err
}
// Register triggers so that if runtime reloads the policies, the
// server sees the change.
config := storage.TriggerConfig{
OnCommit: s.reload,
}
if _, err := s.store.Register(ctx, txn, config); err != nil {
s.store.Abort(ctx, txn)
return nil, err
}
s.partials = map[string]rego.PartialResult{}
s.preparedEvalQueries = newCache(pqMaxCacheSize)
s.defaultDecisionPath = s.generateDefaultDecisionPath()
s.manager.RegisterNDCacheTrigger(s.updateNDCache)
s.Handler = s.initHandlerAuthn(s.Handler)
// compression handler
s.Handler, err = s.initHandlerCompression(s.Handler)
if err != nil {
return nil, err
}
s.DiagnosticHandler = s.initHandlerAuthn(s.DiagnosticHandler)
s.Handler, err = s.initHandlerDecodingLimits(s.Handler)
if err != nil {
return nil, err
}
return s, s.store.Commit(ctx, txn)
}
// Shutdown will attempt to gracefully shutdown each of the http servers
// currently in use by the OPA Server. If any exceed the deadline specified
// by the context an error will be returned.
func (s *Server) Shutdown(ctx context.Context) error {
errChan := make(chan error)
for _, srvr := range s.httpListeners {
go func(s httpListener) {
errChan <- s.Shutdown(ctx)
}(srvr)
}
// wait until each server has finished shutting down
var errorList []error
for range s.httpListeners {
err := <-errChan
if err != nil {
errorList = append(errorList, err)
}
}
if len(errorList) > 0 {
errMsg := "error while shutting down: "
for i, err := range errorList {
//nolint:perfsprint
errMsg += fmt.Sprintf("(%d) %s. ", i, err.Error())
}
return errors.New(errMsg)
}
return nil
}
// WithAddresses sets the listening addresses that the server will bind to.
func (s *Server) WithAddresses(addrs []string) *Server {
s.addrs = addrs
return s
}
// WithDiagnosticAddresses sets the listening addresses that the server will
// bind to and *only* serve read-only diagnostic API's.
func (s *Server) WithDiagnosticAddresses(addrs []string) *Server {
s.diagAddrs = addrs
return s
}
// WithAuthentication sets authentication scheme to use on the server.
func (s *Server) WithAuthentication(scheme AuthenticationScheme) *Server {
s.authentication = scheme
return s
}
// WithAuthorization sets authorization scheme to use on the server.
func (s *Server) WithAuthorization(scheme AuthorizationScheme) *Server {
s.authorization = scheme
return s
}
// WithCertificate sets the server-side certificate that the server will use.
func (s *Server) WithCertificate(cert *tls.Certificate) *Server {
s.cert = cert
return s
}
// WithCertificatePaths sets the server-side certificate and keyfile paths
// that the server will periodically check for changes, and reload if necessary.
func (s *Server) WithCertificatePaths(certFile, keyFile string, refresh time.Duration) *Server {
s.certFile = certFile
s.certKeyFile = keyFile
s.certRefresh = refresh
return s
}
// WithCertPool sets the server-side cert pool that the server will use.
func (s *Server) WithCertPool(pool *x509.CertPool) *Server {
s.certPool = pool
return s
}
// WithTLSConfig sets the TLS configuration used by the server.
func (s *Server) WithTLSConfig(tlsConfig *TLSConfig) *Server {
s.certFile = tlsConfig.CertFile
s.certKeyFile = tlsConfig.KeyFile
s.certPoolFile = tlsConfig.CertPoolFile
return s
}
// WithCertRefresh sets the period on which certs, keys and cert pools are reloaded from disk.
func (s *Server) WithCertRefresh(refresh time.Duration) *Server {
s.certRefresh = refresh
return s
}
// WithStore sets the storage used by the server.
func (s *Server) WithStore(store storage.Store) *Server {
s.store = store
return s
}
// WithMetrics sets the metrics provider used by the server.
func (s *Server) WithMetrics(m Metrics) *Server {
s.metrics = m
return s
}
// WithManager sets the plugins manager used by the server.
func (s *Server) WithManager(manager *plugins.Manager) *Server {
s.manager = manager
return s
}
// WithCompilerErrorLimit sets the limit on the number of compiler errors the server will
// allow.
func (s *Server) WithCompilerErrorLimit(limit int) *Server {
s.errLimit = limit
return s
}
// WithPprofEnabled sets whether pprof endpoints are enabled
func (s *Server) WithPprofEnabled(pprofEnabled bool) *Server {
s.pprofEnabled = pprofEnabled
return s
}
// WithH2CEnabled sets whether h2c ("HTTP/2 cleartext") is enabled for the http listener
func (s *Server) WithH2CEnabled(enabled bool) *Server {
s.h2cEnabled = enabled
return s
}
// WithDecisionLogger sets the decision logger used by the
// server. DEPRECATED. Use WithDecisionLoggerWithErr instead.
func (s *Server) WithDecisionLogger(logger func(context.Context, *Info)) *Server {
s.logger = func(ctx context.Context, info *Info) error {
logger(ctx, info)
return nil
}
return s
}
// WithDecisionLoggerWithErr sets the decision logger used by the server.
func (s *Server) WithDecisionLoggerWithErr(logger func(context.Context, *Info) error) *Server {
s.logger = logger
return s
}
// WithDecisionIDFactory sets a function on the server to generate decision IDs.
func (s *Server) WithDecisionIDFactory(f func() string) *Server {
s.decisionIDFactory = f
return s
}
// WithRuntime sets the runtime data to provide to the evaluation engine.
func (s *Server) WithRuntime(term *ast.Term) *Server {
s.runtime = term
return s
}
// WithRouter sets the mux.Router to attach OPA's HTTP API routes onto. If a
// router is not supplied, the server will create it's own.
func (s *Server) WithRouter(router *http.ServeMux) *Server {
s.router = router
return s
}
func (s *Server) WithMinTLSVersion(minTLSVersion uint16) *Server {
if slices.Contains(supportedTLSVersions, minTLSVersion) {
s.minTLSVersion = minTLSVersion
} else {
s.minTLSVersion = config.DefaultMinTLSVersion
}
return s
}
// WithDistributedTracingOpts sets the options to be used by distributed tracing.
func (s *Server) WithDistributedTracingOpts(opts tracing.Options) *Server {
s.distributedTracingOpts = opts
return s
}
// WithHooks allows passing hooks to the server.
func (s *Server) WithHooks(hs hooks.Hooks) *Server {
s.hooks = hs
return s
}
// WithNDBCacheEnabled sets whether the ND builtins cache is to be used.
func (s *Server) WithNDBCacheEnabled(ndbCacheEnabled bool) *Server {
s.ndbCacheEnabled = ndbCacheEnabled
return s
}
func newEvaluatedRuleTracker() *topdown.EvaluatedRuleTracker {
return &topdown.EvaluatedRuleTracker{}
}
func evaluatedRuleLabels(t *topdown.EvaluatedRuleTracker) []map[string]any {
if t == nil || len(t.Labels) == 0 {
return nil
}
return t.Labels
}
// WithCipherSuites sets the list of enabled TLS 1.01.2 cipher suites.
func (s *Server) WithCipherSuites(cipherSuites *[]uint16) *Server {
s.cipherSuites = cipherSuites
return s
}
// WithUnixSocketPermission sets the permission for the Unix domain socket if used to listen for
// incoming connections. Applies to the sockets the server is listening on including diagnostic API's.
func (s *Server) WithUnixSocketPermission(unixSocketPerm *string) *Server {
s.unixSocketPerm = unixSocketPerm
return s
}
// Listeners returns functions that listen and serve connections.
func (s *Server) Listeners() ([]Loop, error) {
loops := []Loop{}
handlerBindings := map[httpListenerType]struct {
addrs []string
handler http.Handler
}{
defaultListenerType: {s.addrs, s.Handler},
diagnosticListenerType: {s.diagAddrs, s.DiagnosticHandler},
}
for t, binding := range handlerBindings {
for _, addr := range binding.addrs {
l, listener, err := s.getListener(addr, binding.handler, t)
if err != nil {
return nil, err
}
s.httpListeners = append(s.httpListeners, listener)
loops = append(loops, l...)
}
}
return loops, nil
}
// Addrs returns a list of addresses that the server is listening on.
// If the server hasn't been started it will not return an address.
func (s *Server) Addrs() []string {
return s.addrsForType(defaultListenerType)
}
// DiagnosticAddrs returns a list of addresses that the server is listening on
// for the read-only diagnostic API's (eg /health, /metrics, etc)
// If the server hasn't been started it will not return an address.
func (s *Server) DiagnosticAddrs() []string {
return s.addrsForType(diagnosticListenerType)
}
func (s *Server) addrsForType(t httpListenerType) []string {
var addrs []string
for _, l := range s.httpListeners {
a := l.Addr()
if a != "" && l.Type() == t {
addrs = append(addrs, a)
}
}
return addrs
}
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
tc, err := ln.AcceptTCP()
if err != nil {
return nil, err
}
err = tc.SetKeepAlive(true)
if err != nil {
return nil, err
}
err = tc.SetKeepAlivePeriod(3 * time.Minute)
if err != nil {
return nil, err
}
return tc, nil
}
type httpListenerType int
const (
defaultListenerType httpListenerType = iota
diagnosticListenerType
)
type httpListener interface {
Addr() string
ListenAndServe() error
ListenAndServeTLS(certFile, keyFile string) error
Shutdown(context.Context) error
Type() httpListenerType
}
// baseHTTPListener is just a wrapper around http.Server
type baseHTTPListener struct {
s *http.Server
l net.Listener
t httpListenerType
addr string
addrMtx sync.RWMutex
}
var _ httpListener = (*baseHTTPListener)(nil)
func newHTTPListener(srvr *http.Server, t httpListenerType) httpListener {
return &baseHTTPListener{s: srvr, t: t}
}
func newHTTPUnixSocketListener(srvr *http.Server, l net.Listener, t httpListenerType) httpListener {
return &baseHTTPListener{s: srvr, l: l, t: t}
}
func (b *baseHTTPListener) ListenAndServe() error {
addr := b.s.Addr
if addr == "" {
addr = ":http"
}
var err error
b.l, err = net.Listen("tcp", addr)
if err != nil {
return err
}
b.initAddr()
return b.s.Serve(tcpKeepAliveListener{b.l.(*net.TCPListener)})
}
func (b *baseHTTPListener) initAddr() {
b.addrMtx.Lock()
if addr := b.l.(*net.TCPListener).Addr(); addr != nil {
b.addr = addr.String()
}
b.addrMtx.Unlock()
}
func (b *baseHTTPListener) Addr() string {
b.addrMtx.Lock()
defer b.addrMtx.Unlock()
return b.addr
}
func (b *baseHTTPListener) ListenAndServeTLS(certFile, keyFile string) error {
addr := b.s.Addr
if addr == "" {
addr = ":https"
}
var err error
b.l, err = net.Listen("tcp", addr)
if err != nil {
return err
}
b.initAddr()
defer b.l.Close()
return b.s.ServeTLS(tcpKeepAliveListener{b.l.(*net.TCPListener)}, certFile, keyFile)
}
func (b *baseHTTPListener) Shutdown(ctx context.Context) error {
return b.s.Shutdown(ctx)
}
func (b *baseHTTPListener) Type() httpListenerType {
return b.t
}
func (s *Server) getListener(addr string, h http.Handler, t httpListenerType) ([]Loop, httpListener, error) {
parsedURL, err := parseURL(addr, s.cert != nil)
if err != nil {
return nil, nil, err
}
var loops []Loop
var loop Loop
var listener httpListener
switch parsedURL.Scheme {
case "unix":
loop, listener, err = s.getListenerForUNIXSocket(parsedURL, h, t)
loops = []Loop{loop}
case "http":
loop, listener, err = s.getListenerForHTTPServer(parsedURL, h, t)
loops = []Loop{loop}
case "https":
loop, listener, err = s.getListenerForHTTPSServer(parsedURL, h, t)
logger := s.manager.Logger().WithFields(map[string]any{
"cert-file": s.certFile,
"cert-key-file": s.certKeyFile,
})
// if a manual cert refresh period has been set, then use the polling behavior,
// otherwise use the fsnotify default behavior
if s.certRefresh > 0 {
loops = []Loop{loop, s.certLoopPolling(logger)}
} else if s.certFile != "" || s.certPoolFile != "" {
loops = []Loop{loop, s.certLoopNotify(logger)}
}
default:
err = fmt.Errorf("invalid url scheme %q", parsedURL.Scheme)
}
return loops, listener, err
}
func (s *Server) getListenerForHTTPServer(u *url.URL, h http.Handler, t httpListenerType) (Loop, httpListener, error) {
h1s := http.Server{
Addr: u.Host,
Handler: h,
}
if s.h2cEnabled {
p := new(http.Protocols)
p.SetHTTP1(true)
p.SetUnencryptedHTTP2(true)
h1s.Protocols = p
}
l := newHTTPListener(&h1s, t)
return l.ListenAndServe, l, nil
}
func (s *Server) getListenerForHTTPSServer(u *url.URL, h http.Handler, t httpListenerType) (Loop, httpListener, error) {
if s.cert == nil {
return nil, nil, errors.New("TLS certificate required but not supplied")
}
tlsConfig := tls.Config{
GetCertificate: s.getCertificate,
// GetConfigForClient is used to ensure that a fresh config is provided containing the latest cert pool.
// This is not required, but appears to be how connect time updates config should be done:
// https://github.com/golang/go/issues/16066#issuecomment-250606132
GetConfigForClient: func(_ *tls.ClientHelloInfo) (*tls.Config, error) {
s.tlsConfigMtx.Lock()
defer s.tlsConfigMtx.Unlock()
cfg := &tls.Config{
GetCertificate: s.getCertificate,
ClientCAs: s.certPool,
}
if s.authentication == AuthenticationTLS {
cfg.ClientAuth = tls.RequireAndVerifyClientCert
}
if s.minTLSVersion != 0 {
cfg.MinVersion = s.minTLSVersion
} else {
cfg.MinVersion = config.DefaultMinTLSVersion
}
if s.cipherSuites != nil {
cfg.CipherSuites = *s.cipherSuites
}
return cfg, nil
},
}
httpsServer := http.Server{
Addr: u.Host,
Handler: h,
TLSConfig: &tlsConfig,
}
l := newHTTPListener(&httpsServer, t)
httpsLoop := func() error { return l.ListenAndServeTLS("", "") }
return httpsLoop, l, nil
}
func (s *Server) getListenerForUNIXSocket(u *url.URL, h http.Handler, t httpListenerType) (Loop, httpListener, error) {
socketPath := u.Host + u.Path
// Recover @ prefix for abstract Unix sockets (Linux-only).
isAbstract := false
if strings.HasPrefix(u.String(), u.Scheme+"://@") {
socketPath = "@" + socketPath
isAbstract = runtime.GOOS == "linux"
}
if !isAbstract {
// Remove domain socket file in case it already exists.
os.Remove(socketPath)
}
domainSocketServer := http.Server{Handler: h}
if s.h2cEnabled {
p := new(http.Protocols)
p.SetHTTP1(true)
p.SetUnencryptedHTTP2(true)
domainSocketServer.Protocols = p
}
unixListener, err := net.Listen("unix", socketPath)
if err != nil {
return nil, nil, err
}
// Skip chmod for abstract Unix sockets — they exist only in the
// kernel's socket namespace and have no filesystem path to chmod.
if s.unixSocketPerm != nil && !isAbstract {
modeVal, err := strconv.ParseUint(*s.unixSocketPerm, 8, 32)
if err != nil {
return nil, nil, err
}
if err := os.Chmod(socketPath, os.FileMode(modeVal)); err != nil {
return nil, nil, err
}
}
l := newHTTPUnixSocketListener(&domainSocketServer, unixListener, t)
domainSocketLoop := func() error { return domainSocketServer.Serve(unixListener) }
return domainSocketLoop, l, nil
}
func (s *Server) initHandlerAuthn(handler http.Handler) http.Handler {
switch s.authentication {
case AuthenticationToken:
handler = identifier.NewTokenBased(handler)
case AuthenticationTLS:
handler = identifier.NewTLSBased(handler)
}
return handler
}
func (s *Server) initHandlerAuthz(handler http.Handler) http.Handler {
switch s.authorization {
case AuthorizationBasic:
handler = authorizer.NewBasic(
handler,
s.getCompiler,
s.store,
authorizer.Runtime(s.runtime),
authorizer.Decision(s.manager.GetConfig().DefaultAuthorizationDecisionRef),
authorizer.PrintHook(s.manager.PrintHook()),
authorizer.EnablePrintStatements(s.manager.EnablePrintStatements()),
authorizer.InterQueryCache(s.interQueryBuiltinCache),
authorizer.InterQueryValueCache(s.interQueryBuiltinValueCache),
authorizer.URLPathExpectsBodyFunc(s.manager.ExtraAuthorizerRoutes()))
if s.metrics != nil {
handler = s.instrumentHandler(handler.ServeHTTP, PromHandlerAPIAuthz)
}
}
return handler
}
// Enforces request body size limits on incoming requests. For gzipped requests,
// it passes the size limit down the body-reading method via the request
// context.
func (s *Server) initHandlerDecodingLimits(handler http.Handler) (http.Handler, error) {
cfg := s.manager.GetConfig()
var decodingRawConfig []byte
if cfg.Server != nil {
decodingRawConfig = []byte(cfg.Server.Decoding)
}
decodingConfig, err := serverDecodingPlugin.NewConfigBuilder().WithBytes(decodingRawConfig).Parse()
if err != nil {
return nil, err
}
decodingHandler := handlers.DecodingLimitsHandler(handler, *decodingConfig.MaxLength, *decodingConfig.Gzip.MaxLength)
return decodingHandler, nil
}
func (s *Server) initHandlerCompression(handler http.Handler) (http.Handler, error) {
cfg := s.manager.GetConfig()
var encodingRawConfig []byte
if cfg.Server != nil {
encodingRawConfig = []byte(cfg.Server.Encoding)
}
encodingConfig, err := serverEncodingPlugin.NewConfigBuilder().WithBytes(encodingRawConfig).Parse()
if err != nil {
return nil, err
}
compressHandler := handlers.CompressHandler(handler, *encodingConfig.Gzip.MinLength, *encodingConfig.Gzip.CompressionLevel)
return compressHandler, nil
}
func (s *Server) initRouters(ctx context.Context) {
mainRouter := s.router
if mainRouter == nil {
mainRouter = http.NewServeMux()
}
diagRouter := http.NewServeMux()
// authorizer, if configured, needs the iCache to be set up already
cacheConfig := s.manager.InterQueryBuiltinCacheConfig()
s.interQueryBuiltinCache = iCache.NewInterQueryCacheWithContext(ctx, cacheConfig)
s.interQueryBuiltinValueCache = iCache.NewInterQueryValueCache(ctx, cacheConfig)
s.manager.RegisterCacheTrigger(s.updateCacheConfig)
// Add authorization handler. This must come BEFORE authentication handler
// so that the latter can run first.
handlerAuthz := s.initHandlerAuthz(mainRouter)
handlerAuthzDiag := s.initHandlerAuthz(diagRouter)
// All routers get the same base configuration *and* diagnostic API's
for _, router := range []*http.ServeMux{mainRouter, diagRouter} {
if s.metrics != nil {
s.metrics.RegisterEndpoints(func(path, method string, handler http.Handler) {
router.Handle(fmt.Sprintf("%s %s", method, path), handler)
})
}
router.Handle("GET /health", s.instrumentHandler(s.unversionedGetHealth, PromHandlerHealth))
// Use this route to evaluate health policy defined at system.health
// By convention, policy is typically defined at system.health.live and system.health.ready, and is
// evaluated by calling /health/live and /health/ready respectively.
router.Handle("GET /health/{path...}", s.instrumentHandler(s.unversionedGetHealthWithPolicy, PromHandlerHealth))
}
for p, r := range s.manager.ExtraRoutes() {
mainRouter.Handle(p, s.instrumentHandler(r.HandlerFunc, r.PromName))
}
if s.pprofEnabled {
mainRouter.HandleFunc("GET /debug/pprof/", pprof.Index)
mainRouter.Handle("GET /debug/pprof/allocs", pprof.Handler("allocs"))
mainRouter.Handle("GET /debug/pprof/block", pprof.Handler("block"))
mainRouter.Handle("GET /debug/pprof/heap", pprof.Handler("heap"))
mainRouter.Handle("GET /debug/pprof/mutex", pprof.Handler("mutex"))
mainRouter.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline)
mainRouter.HandleFunc("GET /debug/pprof/profile", pprof.Profile)
mainRouter.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol)
mainRouter.HandleFunc("GET /debug/pprof/trace", pprof.Trace)
}
// Only the main mainRouter gets the OPA API's (data, policies, query, etc)
mainRouter.Handle("POST /v0/data/{path...}", s.instrumentHandler(s.v0DataPost, PromHandlerV0Data))
mainRouter.Handle("POST /v0/data", s.instrumentHandler(s.v0DataPost, PromHandlerV0Data))
mainRouter.Handle("DELETE /v1/data/{path...}", s.instrumentHandler(s.v1DataDelete, PromHandlerV1Data))
mainRouter.Handle("PUT /v1/data/{path...}", s.instrumentHandler(s.v1DataPut, PromHandlerV1Data))
mainRouter.Handle("PUT /v1/data", s.instrumentHandler(s.v1DataPut, PromHandlerV1Data))
mainRouter.Handle("GET /v1/data/{path...}", s.instrumentHandler(s.v1DataGet, PromHandlerV1Data))
mainRouter.Handle("GET /v1/data", s.instrumentHandler(s.v1DataGet, PromHandlerV1Data))
mainRouter.Handle("PATCH /v1/data/{path...}", s.instrumentHandler(s.v1DataPatch, PromHandlerV1Data))
mainRouter.Handle("PATCH /v1/data", s.instrumentHandler(s.v1DataPatch, PromHandlerV1Data))
mainRouter.Handle("POST /v1/data/{path...}", s.instrumentHandler(s.v1DataPost, PromHandlerV1Data))
mainRouter.Handle("POST /v1/data", s.instrumentHandler(s.v1DataPost, PromHandlerV1Data))
mainRouter.Handle("GET /v1/policies", s.instrumentHandler(s.v1PoliciesList, PromHandlerV1Policies))
mainRouter.Handle("DELETE /v1/policies/{path...}", s.instrumentHandler(s.v1PoliciesDelete, PromHandlerV1Policies))
mainRouter.Handle("GET /v1/policies/{path...}", s.instrumentHandler(s.v1PoliciesGet, PromHandlerV1Policies))
mainRouter.Handle("PUT /v1/policies/{path...}", s.instrumentHandler(s.v1PoliciesPut, PromHandlerV1Policies))
mainRouter.Handle("GET /v1/query", s.instrumentHandler(s.v1QueryGet, PromHandlerV1Query))
mainRouter.Handle("POST /v1/query", s.instrumentHandler(s.v1QueryPost, PromHandlerV1Query))
mainRouter.Handle("POST /v1/compile", s.instrumentHandler(s.v1CompilePost, PromHandlerV1Compile))
mainRouter.Handle("POST /v1/compile/{path...}", s.instrumentHandler(s.v1CompileFilters, PromHandlerV1Compile))
mainRouter.Handle("GET /v1/compile/{path...}", s.instrumentHandler(s.v1CompileFilters, PromHandlerV1Compile))
mainRouter.Handle("GET /v1/config", s.instrumentHandler(s.v1ConfigGet, PromHandlerV1Config))
mainRouter.Handle("GET /v1/status", s.instrumentHandler(s.v1StatusGet, PromHandlerV1Status))
mainRouter.Handle("POST /{$}", s.instrumentHandler(s.unversionedPost, PromHandlerIndex))
mainRouter.Handle("GET /{$}", s.instrumentHandler(s.indexGet, PromHandlerIndex))
// These are catch all handlers that respond http.StatusMethodNotAllowed for resources that exist but the method is not allowed
mainRouter.Handle("/v0/data/{path...}", s.methodNotAllowedHandler())
mainRouter.Handle("/v0/data", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/data/{path...}", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/data", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/policies", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/policies/{path...}", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/query/{path...}", s.methodNotAllowedHandler())
mainRouter.Handle("/v1/query", s.methodNotAllowedHandler())
// Add authorization handler in the end so that it can run first
s.Handler = handlerAuthz
s.DiagnosticHandler = handlerAuthzDiag
}
func createMiddleware(mw ...func(http.Handler) http.Handler) func(http.Handler) http.Handler {
return func(hnd http.Handler) http.Handler {
next := hnd
for k := len(mw) - 1; k >= 0; k-- {
next = mw[k](next)
}
return next
}
}
func (s *Server) instrumentHandler(handler func(http.ResponseWriter, *http.Request), label string) http.Handler {
httpHandler := handlers.DefaultHandler(createMiddleware(
s.manager.ExtraMiddlewares()...,
)(http.HandlerFunc(handler)))
if len(s.distributedTracingOpts) > 0 {
httpHandler = tracing.NewHandler(httpHandler, label, s.distributedTracingOpts)
}
if s.metrics != nil {
return s.metrics.InstrumentHandler(httpHandler, label)
}
return httpHandler
}
func (s *Server) methodNotAllowedHandler() http.Handler {
return s.instrumentHandler(writer.HTTPStatus(http.StatusMethodNotAllowed), PromHandlerCatch)
}
func (s *Server) execQuery(ctx context.Context, br bundleRevisions, txn storage.Transaction, parsedQuery ast.Body, input ast.Value, rawInput *any, m metrics.Metrics, explainMode types.ExplainModeV1, includeMetrics, includeInstrumentation, pretty bool) (*types.QueryResponseV1, error) {
results := types.QueryResponseV1{}
ctx, logger := s.getDecisionLogger(ctx, br)
var buf *topdown.BufferTracer
if explainMode != types.ExplainOffV1 {
buf = topdown.NewBufferTracer()
}
var ndbCache builtins.NDBCache
if s.ndbCacheEnabled {
ndbCache = builtins.NDBCache{}
}
tracker := newEvaluatedRuleTracker()
opts := []func(*rego.Rego){
rego.Store(s.store),
rego.Transaction(txn),
rego.Compiler(s.getCompiler()),
rego.ParsedQuery(parsedQuery),
rego.ParsedInput(input),
rego.Metrics(m),
rego.Instrument(includeInstrumentation),
rego.QueryTracer(buf),
rego.Runtime(s.runtime),
rego.UnsafeBuiltins(unsafeBuiltinsMap),
rego.InterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.InterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.PrintHook(s.manager.PrintHook()),
rego.EnablePrintStatements(s.manager.EnablePrintStatements()),
rego.DistributedTracingOpts(s.distributedTracingOpts),
rego.NDBuiltinCache(ndbCache),
rego.EvaluatedRuleTracker(tracker),
}
for _, r := range s.manager.GetWasmResolvers() {
for _, entrypoint := range r.Entrypoints() {
opts = append(opts, rego.Resolver(entrypoint, r))
}
}
rego := rego.New(opts...)
output, err := rego.Eval(ctx)
if err != nil {
_ = logger.Log(ctx, txn, "", parsedQuery.String(), rawInput, input, nil, ndbCache, err, m, nil, nil)
return nil, err
}
for _, result := range output {
results.Result = append(results.Result, result.Bindings.WithoutWildcards())
}
if includeMetrics || includeInstrumentation {
results.Metrics = m.All()
}
if explainMode != types.ExplainOffV1 {
results.Explanation = s.getExplainResponse(explainMode, *buf, pretty)
}
var x any = results.Result
if err := logger.Log(ctx, txn, "", parsedQuery.String(), rawInput, input, &x, ndbCache, nil, m, evaluatedRuleLabels(tracker), nil); err != nil {
return nil, err
}
return &results, nil
}
func (*Server) indexGet(w http.ResponseWriter, _ *http.Request) {
_ = indexHTML.Execute(w, struct {
Version string
BuildCommit string
BuildTimestamp string
BuildHostname string
}{
Version: version.Version,
BuildCommit: version.Vcs,
BuildTimestamp: version.Timestamp,
BuildHostname: version.Hostname,
})
}
type bundleRevisions struct {
LegacyRevision string
Revisions map[string]string
}
func getRevisions(ctx context.Context, store storage.Store, txn storage.Transaction) (bundleRevisions, error) {
var err error
var br bundleRevisions
br.Revisions = map[string]string{}
// Check if we still have a legacy bundle manifest in the store
br.LegacyRevision, err = bundle.LegacyReadRevisionFromStore(ctx, store, txn) //nolint:staticcheck
if err != nil && !storage.IsNotFound(err) {
return br, err
}
// read all bundle revisions from storage (if any exist)
names, err := bundle.ReadBundleNamesFromStore(ctx, store, txn)
if err != nil && !storage.IsNotFound(err) {
return br, err
}
for _, name := range names {
r, err := bundle.ReadBundleRevisionFromStore(ctx, store, txn, name)
if err != nil && !storage.IsNotFound(err) {
return br, err
}
br.Revisions[name] = r
}
return br, nil
}
func (s *Server) reload(_ context.Context, _ storage.Transaction, evt storage.TriggerEvent) {
// NOTE(tsandall): We currently rely on the storage txn to provide
// critical sections in the server.
//
// If you modify this function to change any other state on the server, you must
// review the other places in the server where that state is accessed to avoid data
// races--the state must be accessed _after_ a txn has been opened.
// reset some cached info
s.partials = map[string]rego.PartialResult{}
s.preparedEvalQueries = newCache(pqMaxCacheSize)
s.defaultDecisionPath = s.generateDefaultDecisionPath()
if evt.PolicyChanged() {
s.compileUnknownsCache.Purge()
s.compileMaskingRulesCache.Purge()
}
}
func (s *Server) unversionedPost(w http.ResponseWriter, r *http.Request) {
s.v0QueryPath(w, r, "", true)
}
func (s *Server) v0DataPost(w http.ResponseWriter, r *http.Request) {
s.v0QueryPath(w, r, escapedPathValue(r, "path"), false)
}
func (s *Server) v0QueryPath(w http.ResponseWriter, r *http.Request, urlPath string, useDefaultDecisionPath bool) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
decisionID := s.generateDecisionID()
ctx := logging.WithDecisionID(r.Context(), decisionID)
annotateSpan(ctx, decisionID)
input, goInput, err := readInputV0(r)
if err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, fmt.Errorf("unexpected parse error for input: %w", err))
return
}
// Prepare for query.
txn, err := s.store.NewTransaction(ctx)
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
br, err := getRevisions(ctx, s.store, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if useDefaultDecisionPath {
urlPath = s.generateDefaultDecisionPath()
}
ctx, logger := s.getDecisionLogger(ctx, br)
var ndbCache builtins.NDBCache
if s.ndbCacheEnabled {
ndbCache = builtins.NDBCache{}
}
pqID := "v0QueryPath::" + urlPath
preparedQuery, ok := s.getCachedPreparedEvalQuery(pqID, m)
if !ok {
opts := []func(*rego.Rego){
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
}
// Set resolvers on the base Rego object to avoid having them get
// re-initialized, and to propagate them to the prepared query.
for _, r := range s.manager.GetWasmResolvers() {
for _, entrypoint := range r.Entrypoints() {
opts = append(opts, rego.Resolver(entrypoint, r))
}
}
rego, err := s.makeRego(ctx, false, txn, input, urlPath, m, false, nil, opts)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
pq, err := rego.PrepareForEval(ctx)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
preparedQuery = &pq
s.preparedEvalQueries.Insert(pqID, preparedQuery)
}
tracker := newEvaluatedRuleTracker()
evalOpts := []rego.EvalOption{
rego.EvalTransaction(txn),
rego.EvalParsedInput(input),
rego.EvalMetrics(m),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalNDBuiltinCache(ndbCache),
rego.EvalEvaluatedRuleTracker(tracker),
}
rs, err := preparedQuery.Eval(
ctx,
evalOpts...,
)
m.Timer(metrics.ServerHandler).Stop()
// Handle results.
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
if len(rs) == 0 {
ref, err := stringPathToDataRef(urlPath)
if err != nil {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "invalid path: %v", err))
return
}
messageType := types.MsgMissingError
if len(s.getCompiler().GetRulesForVirtualDocument(ref)) > 0 {
messageType = types.MsgFoundUndefinedError
}
errV1 := types.NewErrorV1(types.CodeUndefinedDocument, "%v: %v", messageType, ref)
if err := logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, errV1, m, nil, nil); err != nil {
writer.ErrorAuto(w, err)
return
}
writer.Error(w, http.StatusNotFound, errV1)
return
}
err = logger.Log(ctx, txn, urlPath, "", goInput, input, &rs[0].Expressions[0].Value, ndbCache, nil, m, evaluatedRuleLabels(tracker), nil)
if err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, rs[0].Expressions[0].Value, pretty(r))
}
func (s *Server) getCachedPreparedEvalQuery(key string, m metrics.Metrics) (*rego.PreparedEvalQuery, bool) {
pq, ok := s.preparedEvalQueries.Get(key)
counter := m.Counter(metrics.ServerQueryCacheHit) // Creates the counter on m if it doesn't exist, starts at 0
if ok {
counter.Incr() // Increment counter on hit
return pq.(*rego.PreparedEvalQuery), true
}
return nil, false
}
func (s *Server) canEval(ctx context.Context) bool {
// Create very simple query that binds a single variable.
opts := []func(*rego.Rego){
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
rego.Query("x = 1"),
}
for _, r := range s.manager.GetWasmResolvers() {
for _, ep := range r.Entrypoints() {
opts = append(opts, rego.Resolver(ep, r))
}
}
eval := rego.New(opts...)
// Run evaluation.
rs, err := eval.Eval(ctx)
if err != nil {
return false
}
v, ok := rs[0].Bindings["x"]
if ok {
jsonNumber, ok := v.(json.Number)
if ok && jsonNumber.String() == "1" {
return true
}
}
return false
}
func (*Server) bundlesReady(pluginStatuses map[string]*plugins.Status) bool {
// Look for a discovery plugin first, if it exists and isn't ready
// then don't bother with the others.
// Note: use "discovery" instead of `discovery.Name` to avoid import
// cycle problems..
dpStatus, ok := pluginStatuses["discovery"]
if ok && dpStatus != nil && (dpStatus.State != plugins.StateOK) {
return false
}
// The bundle plugin won't return "OK" until the first activation
// of each configured bundle.
bpStatus, ok := pluginStatuses[bundlePlugin.Name]
if ok && bpStatus != nil && (bpStatus.State != plugins.StateOK) {
return false
}
return true
}
func (s *Server) unversionedGetHealth(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
includeBundleStatus := getBoolParam(r.URL, types.ParamBundleActivationV1, true) || //nolint:staticcheck
getBoolParam(r.URL, types.ParamBundlesActivationV1, true)
includePluginStatus := getBoolParam(r.URL, types.ParamPluginsV1, true)
excludePlugin := getStringSliceParam(r.URL, types.ParamExcludePluginV1)
excludePluginMap := map[string]struct{}{}
for _, name := range excludePlugin {
excludePluginMap[name] = struct{}{}
}
// Ensure the server can evaluate a simple query
if !s.canEval(ctx) {
writeHealthResponse(w, errors.New("unable to perform evaluation"))
return
}
pluginStatuses := s.manager.PluginStatus()
// Ensure that bundles (if configured, and requested to be included in the result)
// have been activated successfully. This will include discovery bundles as well as
// normal bundles that are configured.
if includeBundleStatus && !s.bundlesReady(pluginStatuses) {
// For backwards compatibility we don't return a payload with statuses for the bundle endpoint
writeHealthResponse(w, errors.New("one or more bundles are not activated"))
return
}
if includePluginStatus {
// Ensure that all plugins (if requested to be included in the result) have an OK status.
hasErr := false
for name, status := range pluginStatuses {
if _, exclude := excludePluginMap[name]; exclude {
continue
}
if status != nil && status.State != plugins.StateOK {
hasErr = true
break
}
}
if hasErr {
writeHealthResponse(w, errors.New("one or more plugins are not up"))
return
}
}
writeHealthResponse(w, nil)
}
func (s *Server) unversionedGetHealthWithPolicy(w http.ResponseWriter, r *http.Request) {
pluginStatus := s.manager.PluginStatus()
pluginState := map[string]string{}
// optimistically assume all plugins are ok
allPluginsOk := true
// build input document for health check query
input := func() map[string]any {
s.mtx.Lock()
defer s.mtx.Unlock()
// iterate over plugin status to extract state
for name, status := range pluginStatus {
if status != nil {
pluginState[name] = string(status.State)
// if all plugins have not been in OK state yet, then check to see if plugin state is OKx
if !s.allPluginsOkOnce && status.State != plugins.StateOK {
allPluginsOk = false
}
}
}
// once all plugins are OK, set the allPluginsOkOnce flag to true, indicating that all
// plugins have achieved a "ready" state at least once on the server.
if allPluginsOk {
s.allPluginsOkOnce = true
}
return map[string]any{
"plugin_state": pluginState,
"plugins_ready": s.allPluginsOkOnce,
}
}()
healthDataPath := "/system/health/" + escapedPathValue(r, "path")
healthDataPathQuery, err := stringPathToQuery(healthDataPath)
if err != nil {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "invalid path: %v", err))
return
}
rego := rego.New(
rego.ParsedQuery(healthDataPathQuery),
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
rego.Input(input),
rego.Runtime(s.runtime),
rego.PrintHook(s.manager.PrintHook()),
)
rs, err := rego.Eval(r.Context())
if err != nil {
writeHealthResponse(w, err)
return
}
if len(rs) == 0 {
writeHealthResponse(w, fmt.Errorf("health check (%v) was undefined", healthDataPathQuery))
return
}
result, ok := rs[0].Expressions[0].Value.(bool)
if ok && result {
writeHealthResponse(w, nil)
return
}
writeHealthResponse(w, fmt.Errorf("health check (%v) returned unexpected value", healthDataPathQuery))
}
func writeHealthResponse(w http.ResponseWriter, err error) {
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
if err := json.NewEncoder(w).Encode(types.HealthResponseV1{Error: err.Error()}); err != nil {
writer.ErrorAuto(w, err)
}
return
}
writer.JSONOK(w, types.HealthResponseV1{}, false)
}
func (s *Server) v1CompilePost(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
explainMode := getExplain(r.URL, types.ExplainOffV1)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
m.Timer(metrics.RegoQueryParse).Start()
// decompress the input if sent as zip
body, err := util.ReadMaybeCompressedBody(r)
if err != nil {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "could not decompress the body"))
return
}
request, reqErr := readInputCompilePostV1(body, s.manager.ParserOptions())
if reqErr != nil {
writer.Error(w, http.StatusBadRequest, reqErr)
return
}
m.Timer(metrics.RegoQueryParse).Stop()
c := storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, storage.TransactionParams{Context: c})
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
var buf *topdown.BufferTracer
if explainMode != types.ExplainOffV1 {
buf = topdown.NewBufferTracer()
}
eval := rego.New(
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
rego.Transaction(txn),
rego.ParsedQuery(request.Query),
rego.ParsedInput(request.Input),
rego.ParsedUnknowns(request.Unknowns),
rego.DisableInlining(request.Options.DisableInlining),
rego.NondeterministicBuiltins(request.Options.NondeterminsiticBuiltins),
rego.QueryTracer(buf),
rego.Instrument(includeInstrumentation),
rego.Metrics(m),
rego.Runtime(s.runtime),
rego.UnsafeBuiltins(unsafeBuiltinsMap),
rego.InterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.InterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.PrintHook(s.manager.PrintHook()),
)
pq, err := eval.Partial(ctx)
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileModuleError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
m.Timer(metrics.ServerHandler).Stop()
result := types.CompileResponseV1{}
if includeMetrics(r) || includeInstrumentation {
result.Metrics = m.All()
}
if explainMode != types.ExplainOffV1 {
result.Explanation = s.getExplainResponse(explainMode, *buf, pretty(r))
}
var i any = types.PartialEvaluationResultV1{
Queries: pq.Queries,
Support: pq.Support,
}
result.Result = &i
writer.JSONOK(w, result, pretty(r))
}
func (s *Server) v1DataGet(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
decisionID := s.generateDecisionID()
ctx := logging.WithDecisionID(r.Context(), decisionID)
annotateSpan(ctx, decisionID)
urlPath := escapedPathValue(r, "path")
explainMode := getExplain(r.URL, types.ExplainOffV1)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
provenance := getBoolParam(r.URL, types.ParamProvenanceV1, true)
strictBuiltinErrors := getBoolParam(r.URL, types.ParamStrictBuiltinErrors, true)
m.Timer(metrics.RegoInputParse).Start()
inputs := r.URL.Query()[types.ParamInputV1]
var input ast.Value
var goInput *any
if len(inputs) > 0 {
var err error
input, goInput, err = readInputGetV1(inputs[len(inputs)-1])
if err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
}
m.Timer(metrics.RegoInputParse).Stop()
// Prepare for query.
c := storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, storage.TransactionParams{Context: c})
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
br, err := getRevisions(ctx, s.store, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
ctx, logger := s.getDecisionLogger(ctx, br)
var ndbCache builtins.NDBCache
if s.ndbCacheEnabled {
ndbCache = builtins.NDBCache{}
}
var buf *topdown.BufferTracer
if explainMode != types.ExplainOffV1 {
buf = topdown.NewBufferTracer()
}
pqID := "v1DataGet::"
if strictBuiltinErrors {
pqID += "strict-builtin-errors::"
}
pqID += urlPath
preparedQuery, ok := s.getCachedPreparedEvalQuery(pqID, m)
if !ok {
opts := []func(*rego.Rego){
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
}
for _, r := range s.manager.GetWasmResolvers() {
for _, entrypoint := range r.Entrypoints() {
opts = append(opts, rego.Resolver(entrypoint, r))
}
}
rego, err := s.makeRego(ctx, strictBuiltinErrors, txn, input, urlPath, m, includeInstrumentation, buf, opts)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
pq, err := rego.PrepareForEval(ctx)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
preparedQuery = &pq
s.preparedEvalQueries.Insert(pqID, preparedQuery)
}
tracker := newEvaluatedRuleTracker()
evalOpts := []rego.EvalOption{
rego.EvalTransaction(txn),
rego.EvalParsedInput(input),
rego.EvalMetrics(m),
rego.EvalQueryTracer(buf),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalInstrument(includeInstrumentation),
rego.EvalNDBuiltinCache(ndbCache),
rego.EvalEvaluatedRuleTracker(tracker),
}
rs, err := preparedQuery.Eval(
ctx,
evalOpts...,
)
m.Timer(metrics.ServerHandler).Stop()
// Handle results.
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, nil)
writer.ErrorAuto(w, err)
return
}
result := types.DataResponseV1{
DecisionID: decisionID,
}
if includeMetrics(r) || includeInstrumentation {
result.Metrics = m.All()
}
if provenance {
result.Provenance = s.getProvenance(br)
}
if len(rs) == 0 {
if explainMode == types.ExplainFullV1 {
result.Explanation, err = types.NewTraceV1(lineage.Full(*buf), pretty(r))
if err != nil {
writer.ErrorAuto(w, err)
return
}
}
if err := logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, nil, m, nil, nil); err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, result, pretty(r))
return
}
result.Result = &rs[0].Expressions[0].Value
if explainMode != types.ExplainOffV1 {
result.Explanation = s.getExplainResponse(explainMode, *buf, pretty(r))
}
if err := logger.Log(ctx, txn, urlPath, "", goInput, input, result.Result, ndbCache, nil, m, evaluatedRuleLabels(tracker), nil); err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, result, pretty(r))
}
func (s *Server) v1DataPatch(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
ctx := r.Context()
var ops []types.PatchV1
m.Timer(metrics.RegoInputParse).Start()
if err := util.NewJSONDecoder(r.Body).Decode(&ops); err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
m.Timer(metrics.RegoInputParse).Stop()
patches, err := s.prepareV1PatchSlice(escapedPathValue(r, "path"), ops)
if err != nil {
writer.ErrorAuto(w, err)
return
}
params := storage.WriteParams
params.Context = storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
for _, patch := range patches {
if err := s.checkPathScope(ctx, txn, patch.path); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
if err := s.store.Write(ctx, txn, patch.op, patch.path, patch.value); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
}
if err := ast.CheckPathConflicts(s.getCompiler(), storage.NonEmpty(ctx, s.store, txn)); len(err) > 0 {
s.store.Abort(ctx, txn)
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
if err := s.store.Commit(ctx, txn); err != nil {
writer.ErrorAuto(w, err)
return
}
m.Timer(metrics.ServerHandler).Stop()
if includeMetrics(r) {
result := types.DataResponseV1{
Metrics: m.All(),
}
writer.JSONOK(w, result, false)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) v1DataPost(w http.ResponseWriter, r *http.Request) {
m := s.getMetrics(r)
m.Timer(metrics.ServerHandler).Start()
decisionID := s.generateDecisionID()
ctx := logging.WithDecisionID(r.Context(), decisionID)
annotateSpan(ctx, decisionID)
m.Timer(metrics.RegoInputParse).Start()
parsed, err := readInputPostV1(r)
if err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
input := parsed.Value
goInput := parsed.GoInput
reqMetadata := parsed.Metadata
respMetadata := map[string]any{}
customLog := func() map[string]any {
if len(reqMetadata) == 0 && len(respMetadata) == 0 {
return nil
}
c := make(map[string]any, 2)
if len(reqMetadata) > 0 {
c["request_metadata"] = reqMetadata
}
if len(respMetadata) > 0 {
c["response_metadata"] = respMetadata
}
return c
}
m.Timer(metrics.RegoInputParse).Stop()
txn, err := s.store.NewTransaction(ctx, storage.TransactionParams{Context: storage.NewContext().WithMetrics(m)})
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
provenance := getBoolParam(r.URL, types.ParamProvenanceV1, true)
var logger decisionLogger
var br bundleRevisions
if s.logger != nil || provenance {
br, err = getRevisions(ctx, s.store, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if s.logger != nil {
ctx, logger = s.getDecisionLogger(ctx, br)
}
}
var buf *topdown.BufferTracer
explainMode := getExplain(r.URL, types.ExplainOffV1)
if explainMode != types.ExplainOffV1 {
buf = topdown.NewBufferTracer()
}
var ndbCache builtins.NDBCache
if s.ndbCacheEnabled {
ndbCache = builtins.NDBCache{}
}
urlPath := escapedPathValue(r, "path")
strictBuiltinErrors := getBoolParam(r.URL, types.ParamStrictBuiltinErrors, true)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
pqID := "v1DataPost::"
if strictBuiltinErrors {
pqID = "v1DataPost::strict-builtin-errors::"
}
pqID += urlPath
preparedQuery, ok := s.getCachedPreparedEvalQuery(pqID, m)
if !ok {
opts := []func(*rego.Rego){
rego.Compiler(s.getCompiler()),
rego.Store(s.store),
}
// Set resolvers on the base Rego object to avoid having them get
// re-initialized, and to propagate them to the prepared query.
for _, r := range s.manager.GetWasmResolvers() {
for _, entrypoint := range r.Entrypoints() {
opts = append(opts, rego.Resolver(entrypoint, r))
}
}
rego, err := s.makeRego(ctx, strictBuiltinErrors, txn, input, urlPath, m, includeInstrumentation, buf, opts)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, customLog())
writer.ErrorAuto(w, err)
return
}
pq, err := rego.PrepareForEval(ctx)
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, customLog())
writer.ErrorAuto(w, err)
return
}
preparedQuery = &pq
s.preparedEvalQueries.Insert(pqID, preparedQuery)
}
tracker := newEvaluatedRuleTracker()
evalOpts := []rego.EvalOption{
rego.EvalTransaction(txn),
rego.EvalParsedInput(input),
rego.EvalMetrics(m),
rego.EvalQueryTracer(buf),
rego.EvalInterQueryBuiltinCache(s.interQueryBuiltinCache),
rego.EvalInterQueryBuiltinValueCache(s.interQueryBuiltinValueCache),
rego.EvalInstrument(includeInstrumentation),
rego.EvalNDBuiltinCache(ndbCache),
rego.EvalResponseMetadata(respMetadata),
rego.EvalEvaluatedRuleTracker(tracker),
}
if reqMetadata != nil {
evalOpts = append(evalOpts, rego.EvalRequestMetadata(reqMetadata))
}
rs, err := preparedQuery.Eval(ctx, evalOpts...)
m.Timer(metrics.ServerHandler).Stop()
// Handle results.
if err != nil {
_ = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, err, m, nil, customLog())
writer.ErrorAuto(w, err)
return
}
result := types.DataResponseV1{
DecisionID: decisionID,
}
if len(respMetadata) > 0 {
result.Metadata = respMetadata
}
if input == nil {
result.Warning = types.NewWarning(types.CodeAPIUsageWarn, types.MsgInputKeyMissing)
}
includeMetrics := getBoolParam(r.URL, types.ParamMetricsV1, true)
if includeMetrics || includeInstrumentation {
result.Metrics = m.All()
}
if provenance {
result.Provenance = s.getProvenance(br)
}
if len(rs) == 0 {
if explainMode == types.ExplainFullV1 {
if result.Explanation, err = types.NewTraceV1(lineage.Full(*buf), pretty(r)); err != nil {
writer.ErrorAuto(w, err)
return
}
}
if err = logger.Log(ctx, txn, urlPath, "", goInput, input, nil, ndbCache, nil, m, nil, customLog()); err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, result, pretty(r))
return
}
result.Result = &rs[0].Expressions[0].Value
if explainMode != types.ExplainOffV1 {
result.Explanation = s.getExplainResponse(explainMode, *buf, pretty(r))
}
if err := logger.Log(ctx, txn, urlPath, "", goInput, input, result.Result, ndbCache, nil, m, evaluatedRuleLabels(tracker), customLog()); err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, result, pretty(r))
}
func escapedPathValue(r *http.Request, key string) string {
pathValue := r.PathValue(key)
escaped := r.URL.EscapedPath()
if !strings.Contains(escaped, "%") {
return pathValue
}
i := strings.Index(r.URL.Path, pathValue)
if i == -1 || i > len(escaped) {
return pathValue
}
return escaped[i:]
}
func (s *Server) v1DataPut(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
ctx := r.Context()
m.Timer(metrics.RegoInputParse).Start()
var value any
if err := util.NewJSONDecoder(r.Body).Decode(&value); err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
m.Timer(metrics.RegoInputParse).Stop()
pv := escapedPathValue(r, "path")
path, ok := storage.ParsePathEscaped("/" + strings.Trim(pv, "/"))
if !ok {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "bad path: %v", pv))
return
}
params := storage.WriteParams
params.Context = storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if err := s.checkPathScope(ctx, txn, path); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
_, err = s.store.Read(ctx, txn, path)
if err != nil {
if !storage.IsNotFound(err) {
s.abortAuto(ctx, txn, w, err)
return
}
if len(path) > 0 {
if err := storage.MakeDir(ctx, s.store, txn, path[:len(path)-1]); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
}
} else if r.Header.Get("If-None-Match") == "*" {
s.store.Abort(ctx, txn)
w.WriteHeader(http.StatusNotModified)
return
}
if err := s.store.Write(ctx, txn, storage.AddOp, path, value); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
if err := ast.CheckPathConflicts(s.getCompiler(), storage.NonEmpty(ctx, s.store, txn)); len(err) > 0 {
s.store.Abort(ctx, txn)
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
if err := s.store.Commit(ctx, txn); err != nil {
writer.ErrorAuto(w, err)
return
}
m.Timer(metrics.ServerHandler).Stop()
if includeMetrics(r) {
result := types.DataResponseV1{
Metrics: m.All(),
}
writer.JSONOK(w, result, false)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) v1DataDelete(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
ctx := r.Context()
pv := escapedPathValue(r, "path")
path, ok := storage.ParsePathEscaped("/" + strings.Trim(pv, "/"))
if !ok {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "bad path: %v", pv))
return
}
params := storage.WriteParams
params.Context = storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if err := s.checkPathScope(ctx, txn, path); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
_, err = s.store.Read(ctx, txn, path)
if err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
if err := s.store.Write(ctx, txn, storage.RemoveOp, path, nil); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
if err := s.store.Commit(ctx, txn); err != nil {
writer.ErrorAuto(w, err)
return
}
m.Timer(metrics.ServerHandler).Stop()
if includeMetrics(r) {
result := types.DataResponseV1{
Metrics: m.All(),
}
writer.JSONOK(w, result, false)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) v1PoliciesDelete(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("path")
m := metrics.New()
params := storage.WriteParams
params.Context = storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(r.Context(), params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if err := s.checkPolicyIDScope(r.Context(), txn, id); err != nil {
s.abortAuto(r.Context(), txn, w, err)
return
}
modules, err := s.loadModules(r.Context(), txn)
if err != nil {
s.abortAuto(r.Context(), txn, w, err)
return
}
delete(modules, id)
c := ast.NewCompiler().SetErrorLimit(s.errLimit)
m.Timer(metrics.RegoModuleCompile).Start()
if c.Compile(modules); c.Failed() {
s.abort(r.Context(), txn, func() {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidOperation, types.MsgCompileModuleError).WithASTErrors(c.Errors))
})
return
}
m.Timer(metrics.RegoModuleCompile).Stop()
if err := s.store.DeletePolicy(r.Context(), txn, id); err != nil {
s.abortAuto(r.Context(), txn, w, err)
return
}
if err := s.store.Commit(r.Context(), txn); err != nil {
writer.ErrorAuto(w, err)
return
}
resp := types.PolicyDeleteResponseV1{}
if includeMetrics(r) {
resp.Metrics = m.All()
}
writer.JSONOK(w, resp, pretty(r))
}
func (s *Server) v1PoliciesGet(w http.ResponseWriter, r *http.Request) {
txn, err := s.store.NewTransaction(r.Context())
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(r.Context(), txn)
path := r.PathValue("path")
bs, err := s.store.GetPolicy(r.Context(), txn, path)
if err != nil {
writer.ErrorAuto(w, err)
return
}
resp := types.PolicyGetResponseV1{
Result: types.PolicyV1{
ID: path,
Raw: string(bs),
AST: s.getCompiler().Modules[path],
},
}
writer.JSONOK(w, resp, pretty(r))
}
func (s *Server) v1PoliciesList(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
txn, err := s.store.NewTransaction(ctx)
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
policies := []types.PolicyV1{}
c := s.getCompiler()
// Only return policies from the store, the compiler
// may contain additional partially compiled modules.
ids, err := s.store.ListPolicies(ctx, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
for _, id := range ids {
bs, err := s.store.GetPolicy(ctx, txn, id)
if err != nil {
writer.ErrorAuto(w, err)
return
}
policy := types.PolicyV1{
ID: id,
Raw: string(bs),
AST: c.Modules[id],
}
policies = append(policies, policy)
}
writer.JSONOK(w, types.PolicyListResponseV1{Result: policies}, pretty(r))
}
func (s *Server) v1PoliciesPut(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
id := r.PathValue("path")
includeMetrics := includeMetrics(r)
m := metrics.New()
m.Timer("server_read_bytes").Start()
buf, err := io.ReadAll(r.Body)
if err != nil {
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
return
}
m.Timer("server_read_bytes").Stop()
params := storage.WriteParams
params.Context = storage.NewContext().WithMetrics(m)
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
if err := s.checkPolicyIDScope(ctx, txn, id); err != nil && !storage.IsNotFound(err) {
s.abortAuto(ctx, txn, w, err)
return
}
if bs, err := s.store.GetPolicy(ctx, txn, id); err != nil {
if !storage.IsNotFound(err) {
s.abortAuto(ctx, txn, w, err)
return
}
} else if bytes.Equal(buf, bs) {
s.store.Abort(ctx, txn)
resp := types.PolicyPutResponseV1{}
if includeMetrics {
resp.Metrics = m.All()
}
writer.JSONOK(w, resp, pretty(r))
return
}
m.Timer(metrics.RegoModuleParse).Start()
parsedMod, err := ast.ParseModuleWithOpts(id, string(buf), s.manager.ParserOptions())
m.Timer(metrics.RegoModuleParse).Stop()
if err != nil {
s.store.Abort(ctx, txn)
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileModuleError).WithASTErrors(err))
default:
writer.ErrorString(w, http.StatusBadRequest, types.CodeInvalidParameter, err)
}
return
}
if parsedMod == nil {
s.store.Abort(ctx, txn)
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "empty module"))
return
}
if err := s.checkPolicyPackageScope(ctx, txn, parsedMod.Package); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
modules, err := s.loadModules(ctx, txn)
if err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
modules[id] = parsedMod
c := ast.NewCompiler().
SetErrorLimit(s.errLimit).
WithPathConflictsCheck(storage.NonEmpty(ctx, s.store, txn)).
WithEnablePrintStatements(s.manager.EnablePrintStatements())
m.Timer(metrics.RegoModuleCompile).Start()
if c.Compile(modules); c.Failed() {
s.abort(ctx, txn, func() {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileModuleError).WithASTErrors(c.Errors))
})
return
}
m.Timer(metrics.RegoModuleCompile).Stop()
if err := s.store.UpsertPolicy(ctx, txn, id, buf); err != nil {
s.abortAuto(ctx, txn, w, err)
return
}
if err := s.store.Commit(ctx, txn); err != nil {
writer.ErrorAuto(w, err)
return
}
resp := types.PolicyPutResponseV1{}
if includeMetrics {
resp.Metrics = m.All()
}
writer.JSONOK(w, resp, pretty(r))
}
func (s *Server) v1QueryGet(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
decisionID := s.generateDecisionID()
ctx := logging.WithDecisionID(r.Context(), decisionID)
annotateSpan(ctx, decisionID)
values := r.URL.Query()
qStrs := values[types.ParamQueryV1]
if len(qStrs) == 0 {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "missing parameter 'q'"))
return
}
qStr := qStrs[len(qStrs)-1]
parsedQuery, err := validateQuery(qStr, s.manager.ParserOptions())
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgParseQueryError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
explainMode := getExplain(r.URL, types.ExplainOffV1)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
params := storage.TransactionParams{Context: storage.NewContext().WithMetrics(m)}
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
br, err := getRevisions(ctx, s.store, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
pretty := pretty(r)
results, err := s.execQuery(ctx, br, txn, parsedQuery, nil, nil, m, explainMode, includeMetrics(r), includeInstrumentation, pretty)
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileQueryError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
writer.JSONOK(w, results, pretty)
}
func (s *Server) v1QueryPost(w http.ResponseWriter, r *http.Request) {
m := metrics.New()
m.Timer(metrics.ServerHandler).Start()
decisionID := s.generateDecisionID()
ctx := logging.WithDecisionID(r.Context(), decisionID)
annotateSpan(ctx, decisionID)
var request types.QueryRequestV1
err := util.NewJSONDecoder(r.Body).Decode(&request)
if err != nil {
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, "error(s) occurred while decoding request: %v", err.Error()))
return
}
qStr := request.Query
parsedQuery, err := validateQuery(qStr, s.manager.ParserOptions())
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgParseQueryError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
pretty := pretty(r)
explainMode := getExplain(r.URL, types.ExplainOffV1)
includeMetrics := includeMetrics(r)
includeInstrumentation := getBoolParam(r.URL, types.ParamInstrumentV1, true)
var input ast.Value
if request.Input != nil {
input, err = ast.InterfaceToValue(*request.Input)
if err != nil {
writer.ErrorAuto(w, err)
return
}
}
params := storage.TransactionParams{Context: storage.NewContext().WithMetrics(m)}
txn, err := s.store.NewTransaction(ctx, params)
if err != nil {
writer.ErrorAuto(w, err)
return
}
defer s.store.Abort(ctx, txn)
br, err := getRevisions(ctx, s.store, txn)
if err != nil {
writer.ErrorAuto(w, err)
return
}
results, err := s.execQuery(ctx, br, txn, parsedQuery, input, request.Input, m, explainMode, includeMetrics, includeInstrumentation, pretty)
if err != nil {
switch err := err.(type) {
case ast.Errors:
writer.Error(w, http.StatusBadRequest, types.NewErrorV1(types.CodeInvalidParameter, types.MsgCompileQueryError).WithASTErrors(err))
default:
writer.ErrorAuto(w, err)
}
return
}
m.Timer(metrics.ServerHandler).Stop()
if includeMetrics || includeInstrumentation {
results.Metrics = m.All()
}
writer.JSONOK(w, results, pretty)
}
func (s *Server) v1ConfigGet(w http.ResponseWriter, r *http.Request) {
result, err := s.manager.GetConfig().ActiveConfig()
if err != nil {
writer.ErrorAuto(w, err)
return
}
writer.JSONOK(w, types.ConfigResponseV1{Result: &result}, pretty(r))
}
func (s *Server) v1StatusGet(w http.ResponseWriter, r *http.Request) {
p := status.Lookup(s.manager)
if p == nil {
writer.ErrorString(w, http.StatusInternalServerError, types.CodeInternal, errors.New("status plugin not enabled"))
return
}
var st any = p.Snapshot()
writer.JSONOK(w, types.StatusResponseV1{Result: &st}, pretty(r))
}
func (s *Server) checkPolicyIDScope(ctx context.Context, txn storage.Transaction, id string) error {
bs, err := s.store.GetPolicy(ctx, txn, id)
if err != nil {
return err
}
module, err := ast.ParseModuleWithOpts(id, string(bs), s.manager.ParserOptions())
if err != nil {
return err
}
return s.checkPolicyPackageScope(ctx, txn, module.Package)
}
func (s *Server) checkPolicyPackageScope(ctx context.Context, txn storage.Transaction, pkg *ast.Package) error {
path, err := pkg.Path.Ptr()
if err != nil {
return err
}
spath, ok := storage.ParsePathEscaped("/" + path)
if !ok {
return types.BadRequestErr("invalid package path: cannot determine scope")
}
return s.checkPathScope(ctx, txn, spath)
}
func (s *Server) getMetrics(r *http.Request) metrics.Metrics {
metricsInQuery := getBoolParam(r.URL, types.ParamMetricsV1, true)
instrumentationInQuery := getBoolParam(r.URL, types.ParamInstrumentV1, true)
if s.logger == nil && !metricsInQuery && !instrumentationInQuery {
return metrics.NoOp()
}
return metrics.New()
}
func (s *Server) checkPathScope(ctx context.Context, txn storage.Transaction, path storage.Path) error {
names, err := bundle.ReadBundleNamesFromStore(ctx, s.store, txn)
if err != nil {
if !storage.IsNotFound(err) {
return err
}
return nil
}
bundleRoots := map[string][]string{}
for _, name := range names {
roots, err := bundle.ReadBundleRootsFromStore(ctx, s.store, txn, name)
if err != nil && !storage.IsNotFound(err) {
return err
}
bundleRoots[name] = roots
}
spath := strings.Trim(path.String(), "/")
if spath == "" && len(bundleRoots) > 0 {
return types.BadRequestErr("can't write to document root with bundle roots configured")
}
spathParts := strings.Split(spath, "/")
for name, roots := range bundleRoots {
if roots == nil {
return types.BadRequestErr(fmt.Sprintf("all paths owned by bundle %q", name))
}
for _, root := range roots {
if root == "" {
return types.BadRequestErr(fmt.Sprintf("all paths owned by bundle %q", name))
}
if isPathOwned(spathParts, strings.Split(root, "/")) {
return types.BadRequestErr(fmt.Sprintf("path %v is owned by bundle %q", spath, name))
}
}
}
return nil
}
func (s *Server) getDecisionLogger(ctx context.Context, br bundleRevisions) (context.Context, decisionLogger) {
var logger decisionLogger
if intermediateResultsEnabled {
ctx = context.WithValue(ctx, IntermediateResultsContextKey{}, make(map[string]any))
}
// For backwards compatibility use `revision` as needed.
if s.hasLegacyBundle(br) {
logger.revision = br.LegacyRevision
} else {
logger.revisions = br.Revisions
}
logger.logger = s.logger
return ctx, logger
}
func (*Server) getExplainResponse(explainMode types.ExplainModeV1, trace []*topdown.Event, pretty bool) (explanation types.TraceV1) {
switch explainMode {
case types.ExplainNotesV1:
var err error
explanation, err = types.NewTraceV1(lineage.Notes(trace), pretty)
if err != nil {
break
}
case types.ExplainFailsV1:
var err error
explanation, err = types.NewTraceV1(lineage.Fails(trace), pretty)
if err != nil {
break
}
case types.ExplainFullV1:
var err error
explanation, err = types.NewTraceV1(lineage.Full(trace), pretty)
if err != nil {
break
}
case types.ExplainDebugV1:
var err error
explanation, err = types.NewTraceV1(lineage.Debug(trace), pretty)
if err != nil {
break
}
}
return explanation
}
func (s *Server) abort(ctx context.Context, txn storage.Transaction, finish func()) {
s.store.Abort(ctx, txn)
finish()
}
func (s *Server) abortAuto(ctx context.Context, txn storage.Transaction, w http.ResponseWriter, err error) {
s.abort(ctx, txn, func() { writer.ErrorAuto(w, err) })
}
func (s *Server) loadModules(ctx context.Context, txn storage.Transaction) (map[string]*ast.Module, error) {
ids, err := s.store.ListPolicies(ctx, txn)
if err != nil {
return nil, err
}
modules := make(map[string]*ast.Module, len(ids))
for _, id := range ids {
bs, err := s.store.GetPolicy(ctx, txn, id)
if err != nil {
return nil, err
}
parsed, err := ast.ParseModuleWithOpts(id, string(bs), s.manager.ParserOptions())
if err != nil {
return nil, err
}
modules[id] = parsed
}
return modules, nil
}
func (s *Server) getCompiler() *ast.Compiler {
return s.manager.GetCompiler()
}
func (s *Server) makeRego(_ context.Context,
strictBuiltinErrors bool,
txn storage.Transaction,
input ast.Value,
urlPath string,
m metrics.Metrics,
instrument bool,
tracer topdown.QueryTracer,
opts []func(*rego.Rego),
) (*rego.Rego, error) {
query, err := stringPathToQuery(urlPath)
if err != nil {
return nil, types.NewErrorV1(types.CodeInvalidParameter, "invalid path: %v", err)
}
opts = append(
opts,
rego.Transaction(txn),
rego.ParsedQuery(query),
rego.ParsedInput(input),
rego.Metrics(m),
rego.QueryTracer(tracer),
rego.Instrument(instrument),
rego.Runtime(s.runtime),
rego.UnsafeBuiltins(unsafeBuiltinsMap),
rego.StrictBuiltinErrors(strictBuiltinErrors),
rego.PrintHook(s.manager.PrintHook()),
rego.DistributedTracingOpts(s.distributedTracingOpts),
)
return rego.New(opts...), nil
}
func stringPathToQuery(urlPath string) (ast.Body, error) {
ref, err := stringPathToDataRef(urlPath)
if err != nil {
return nil, err
}
return parseRefQuery(ref.String())
}
// parseRefQuery parses a string into a query ast.Body.
// The resulting query must be comprised of a single ref, or an error will be returned.
func parseRefQuery(str string) (ast.Body, error) {
query, err := ast.ParseBody(str)
if err != nil {
return nil, errors.New("failed to parse query")
}
// assert the query is exactly one statement
if l := len(query); l == 0 {
return nil, errors.New("no ref")
} else if l > 1 {
return nil, errors.New("complex query")
}
// assert the single statement is a lone ref
expr := query[0]
switch t := expr.Terms.(type) {
case *ast.Term:
switch t.Value.(type) {
case ast.Ref:
return query, nil
}
}
return nil, errors.New("complex query")
}
func (*Server) prepareV1PatchSlice(root string, ops []types.PatchV1) (result []patchImpl, err error) {
root = "/" + strings.Trim(root, "/")
for _, op := range ops {
impl := patchImpl{
value: op.Value,
}
// Map patch operation.
switch op.Op {
case "add":
impl.op = storage.AddOp
case "remove":
impl.op = storage.RemoveOp
case "replace":
impl.op = storage.ReplaceOp
default:
return nil, types.BadPatchOperationErr(op.Op)
}
// Construct patch path.
path := strings.Trim(op.Path, "/")
if len(path) > 0 {
if root == "/" {
path = root + path
} else {
path = root + "/" + path
}
} else {
path = root
}
var ok bool
impl.path, ok = patch.ParsePatchPathEscaped(path)
if !ok {
return nil, types.BadPatchPathErr(op.Path)
}
result = append(result, impl)
}
return result, nil
}
func (s *Server) generateDecisionID() string {
if s.decisionIDFactory != nil {
return s.decisionIDFactory()
}
return ""
}
func (s *Server) getProvenance(br bundleRevisions) *types.ProvenanceV1 {
p := &types.ProvenanceV1{
Version: version.Version,
Vcs: version.Vcs,
Timestamp: version.Timestamp,
Hostname: version.Hostname,
}
// For backwards compatibility, if the bundles are using the old
// style config we need to fill in the older `Revision` field.
// Otherwise use the newer `Bundles` keyword.
if s.hasLegacyBundle(br) {
p.Revision = br.LegacyRevision
} else {
p.Bundles = map[string]types.ProvenanceBundleV1{}
for name, revision := range br.Revisions {
p.Bundles[name] = types.ProvenanceBundleV1{Revision: revision}
}
}
return p
}
func (s *Server) hasLegacyBundle(br bundleRevisions) bool {
bp := bundlePlugin.Lookup(s.manager)
return br.LegacyRevision != "" || (bp != nil && !bp.Config().IsMultiBundle())
}
func (s *Server) generateDefaultDecisionPath() string {
// Assume the path is safe to transition back to a url
p, _ := s.manager.GetConfig().DefaultDecisionRef().Ptr()
return p
}
func isPathOwned(path, root []string) bool {
for i := 0; i < len(path) && i < len(root); i++ {
if path[i] != root[i] {
return false
}
}
return true
}
func (s *Server) updateCacheConfig(cacheConfig *iCache.Config) {
s.interQueryBuiltinCache.UpdateConfig(cacheConfig)
s.interQueryBuiltinValueCache.UpdateConfig(cacheConfig)
}
func (s *Server) updateNDCache(enabled bool) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.ndbCacheEnabled = enabled
}
func stringPathToDataRef(s string) (ast.Ref, error) {
r, err := stringPathToRef(s)
if err != nil {
return nil, err
}
result := make(ast.Ref, 1, 1+len(r))
result[0] = ast.DefaultRootDocument
return append(result, r...), nil
}
func stringPathToRef(s string) (ast.Ref, error) {
r := ast.Ref{}
if len(s) == 0 {
return r, nil
}
for x := range strings.SplitSeq(s, "/") {
if x == "" {
continue
}
if y, err := url.PathUnescape(x); err == nil {
x = y
}
if strings.Contains(x, "\"") {
return nil, fmt.Errorf("invalid ref term '%s'", x)
}
i, err := strconv.Atoi(x)
if err != nil {
r = append(r, ast.StringTerm(x))
} else {
r = append(r, ast.InternedTerm(i))
}
}
return r, nil
}
func validateQuery(query string, opts ast.ParserOptions) (ast.Body, error) {
return ast.ParseBodyWithOpts(query, opts)
}
func getBoolParam(url *url.URL, name string, ifEmpty bool) bool {
if url.RawQuery == "" {
return false
}
p, ok := url.Query()[name]
if !ok {
return false
}
// Query params w/o values are represented as slice (of len 1) with an
// empty string.
if len(p) == 1 && p[0] == "" {
return ifEmpty
}
for _, x := range p {
if strings.EqualFold(x, "true") {
return true
}
}
return false
}
func getStringSliceParam(url *url.URL, name string) []string {
p, ok := url.Query()[name]
if !ok {
return nil
}
// Query params w/o values are represented as slice (of len 1) with an
// empty string.
if len(p) == 1 && p[0] == "" {
return nil
}
return p
}
func getExplain(url *url.URL, zero types.ExplainModeV1) types.ExplainModeV1 {
if url.RawQuery == "" {
return zero
}
for _, x := range url.Query()[types.ParamExplainV1] {
switch x {
case string(types.ExplainNotesV1):
return types.ExplainNotesV1
case string(types.ExplainFailsV1):
return types.ExplainFailsV1
case string(types.ExplainFullV1):
return types.ExplainFullV1
case string(types.ExplainDebugV1):
return types.ExplainDebugV1
}
}
return zero
}
func readInputV0(r *http.Request) (ast.Value, *any, error) {
parsed, ok := authorizer.GetBodyOnContext(r.Context())
if ok {
v, err := ast.InterfaceToValue(parsed)
return v, &parsed, err
}
// decompress the input if sent as zip
bodyBytes, err := util.ReadMaybeCompressedBody(r)
if err != nil {
return nil, nil, fmt.Errorf("could not decompress the body: %w", err)
}
var x any
if strings.Contains(r.Header.Get("Content-Type"), "yaml") {
if len(bodyBytes) > 0 {
if err = util.Unmarshal(bodyBytes, &x); err != nil {
return nil, nil, fmt.Errorf("body contains malformed input document: %w", err)
}
}
} else {
dec := util.NewJSONDecoder(bytes.NewBuffer(bodyBytes))
if err := dec.Decode(&x); err != nil && err != io.EOF {
return nil, nil, fmt.Errorf("body contains malformed input document: %w", err)
}
}
v, err := ast.InterfaceToValue(x)
return v, &x, err
}
func readInputGetV1(str string) (ast.Value, *any, error) {
var input any
if err := util.UnmarshalJSON([]byte(str), &input); err != nil {
return nil, nil, fmt.Errorf("parameter contains malformed input document: %w", err)
}
v, err := ast.InterfaceToValue(input)
return v, &input, err
}
type parsedInput struct {
Value ast.Value
GoInput *any
Metadata map[string]any
}
func readInputPostV1(r *http.Request) (*parsedInput, error) {
parsed, ok := authorizer.GetBodyOnContext(r.Context())
if ok {
if obj, ok := parsed.(map[string]any); ok {
if input, ok := obj["input"]; ok {
v, err := ast.InterfaceToValue(input)
return &parsedInput{Value: v, GoInput: &input}, err
}
}
return &parsedInput{}, nil
}
var request types.DataRequestV1
// decompress the input if sent as zip
bodyBytes, err := util.ReadMaybeCompressedBody(r)
if err != nil {
return nil, fmt.Errorf("could not decompress the body: %w", err)
}
ct := r.Header.Get("Content-Type")
// There is no standard for yaml mime-type so we just look for
// anything related
if strings.Contains(ct, "yaml") {
if len(bodyBytes) > 0 {
if err = util.Unmarshal(bodyBytes, &request); err != nil {
return nil, fmt.Errorf("body contains malformed input document: %w", err)
}
}
} else {
dec := util.NewJSONDecoder(bytes.NewBuffer(bodyBytes))
if err := dec.Decode(&request); err != nil && err != io.EOF {
return nil, fmt.Errorf("body contains malformed input document: %w", err)
}
}
if request.Input == nil {
return &parsedInput{Metadata: request.Metadata}, nil
}
v, err := ast.InterfaceToValue(*request.Input)
return &parsedInput{Value: v, GoInput: request.Input, Metadata: request.Metadata}, err
}
type compileRequest struct {
Query ast.Body
Input ast.Value
Unknowns []*ast.Term
Options compileRequestOptions
}
type compileRequestOptions struct {
DisableInlining []string
NondeterminsiticBuiltins bool
}
func readInputCompilePostV1(reqBytes []byte, queryParserOptions ast.ParserOptions) (*compileRequest, *types.ErrorV1) {
var request types.CompileRequestV1
err := util.NewJSONDecoder(bytes.NewBuffer(reqBytes)).Decode(&request)
if err != nil {
return nil, types.NewErrorV1(types.CodeInvalidParameter, "error(s) occurred while decoding request: %v", err.Error())
}
query, err := ast.ParseBodyWithOpts(request.Query, queryParserOptions)
if err != nil {
switch err := err.(type) {
case ast.Errors:
return nil, types.NewErrorV1(types.CodeInvalidParameter, types.MsgParseQueryError).WithASTErrors(err)
default:
return nil, types.NewErrorV1(types.CodeInvalidParameter, "%v: %v", types.MsgParseQueryError, err)
}
} else if len(query) == 0 {
return nil, types.NewErrorV1(types.CodeInvalidParameter, "missing required 'query' value")
}
var input ast.Value
if request.Input != nil {
input, err = ast.InterfaceToValue(*request.Input)
if err != nil {
return nil, types.NewErrorV1(types.CodeInvalidParameter, "error(s) occurred while converting input: %v", err)
}
}
var unknowns []*ast.Term
if request.Unknowns != nil {
unknowns = make([]*ast.Term, len(*request.Unknowns))
for i, s := range *request.Unknowns {
unknowns[i], err = ast.ParseTerm(s)
if err != nil {
return nil, types.NewErrorV1(types.CodeInvalidParameter, "error(s) occurred while parsing unknowns: %v", err)
}
}
}
return &compileRequest{
Query: query,
Input: input,
Unknowns: unknowns,
Options: compileRequestOptions{
DisableInlining: request.Options.DisableInlining,
NondeterminsiticBuiltins: request.Options.NondeterministicBuiltins,
},
}, nil
}
var indexHTML, _ = template.New("index").Parse(`
<html>
<head>
</head>
</body>
<pre>
________ ________ ________
|\ __ \ |\ __ \ |\ __ \
\ \ \|\ \ \ \ \|\ \ \ \ \|\ \
\ \ \\\ \ \ \ ____\ \ \ __ \
\ \ \\\ \ \ \ \___| \ \ \ \ \
\ \_______\ \ \__\ \ \__\ \__\
\|_______| \|__| \|__|\|__|
</pre>
Open Policy Agent - An open source project to policy-enable your service.<br>
<br>
Version: {{ .Version }}<br>
Build Commit: {{ .BuildCommit }}<br>
Build Timestamp: {{ .BuildTimestamp }}<br>
Build Hostname: {{ .BuildHostname }}<br>
</body>
</html>
`)
type decisionLogger struct {
revisions map[string]string
revision string // Deprecated: Use `revisions` instead.
logger func(context.Context, *Info) error
}
func (l decisionLogger) Log(
ctx context.Context,
txn storage.Transaction,
path string,
query string,
goInput *any,
astInput ast.Value,
goResults *any,
ndbCache builtins.NDBCache,
err error,
m metrics.Metrics,
evaluatedRuleLabels []map[string]any,
custom map[string]any,
) error {
if l.logger == nil {
return nil
}
bundles := map[string]BundleInfo{}
for name, rev := range l.revisions {
bundles[name] = BundleInfo{Revision: rev}
}
rctx := logging.RequestContext{}
if r, ok := logging.FromContext(ctx); ok {
rctx = *r
}
decisionID, _ := logging.DecisionIDFromContext(ctx)
var httpRctx logging.HTTPRequestContext
httpRctxVal, _ := logging.HTTPRequestContextFromContext(ctx)
if httpRctxVal != nil {
httpRctx = *httpRctxVal
}
info := &Info{
Txn: txn,
Revision: l.revision,
Bundles: bundles,
Timestamp: time.Now().UTC(),
DecisionID: decisionID,
RemoteAddr: rctx.ClientAddr,
HTTPRequestContext: httpRctx,
Path: path,
Query: query,
Input: goInput,
InputAST: astInput,
Results: goResults,
Error: err,
Metrics: m,
RequestID: rctx.ReqID,
EvaluatedRuleLabels: evaluatedRuleLabels,
Custom: custom,
}
if ndbCache != nil {
x, err := ast.JSON(ndbCache.AsValue())
if err != nil {
return err
}
info.NDBuiltinCache = &x
}
sctx := trace.SpanFromContext(ctx).SpanContext()
if sctx.IsValid() {
info.TraceID = sctx.TraceID().String()
info.SpanID = sctx.SpanID().String()
}
if intermediateResultsEnabled {
if iresults, ok := ctx.Value(IntermediateResultsContextKey{}).(map[string]any); ok {
info.IntermediateResults = iresults
}
}
if l.logger != nil {
if err := l.logger(ctx, info); err != nil {
return fmt.Errorf("decision_logs: %w", err)
}
}
return nil
}
type patchImpl struct {
path storage.Path
op storage.PatchOp
value any
}
func parseURL(s string, useHTTPSByDefault bool) (*url.URL, error) {
if !strings.Contains(s, "://") {
scheme := "http://"
if useHTTPSByDefault {
scheme = "https://"
}
s = scheme + s
}
return url.Parse(s)
}
func annotateSpan(ctx context.Context, decisionID string) {
if decisionID != "" {
trace.SpanFromContext(ctx).SetAttributes(attribute.String(otelDecisionIDAttr, decisionID))
}
}
func pretty(r *http.Request) bool {
return getBoolParam(r.URL, types.ParamPrettyV1, true)
}
func includeMetrics(r *http.Request) bool {
return getBoolParam(r.URL, types.ParamMetricsV1, true)
}