authz: add onPolicyUpdate callback to authz file watcher - #9142
Conversation
|
|
bc68034 to
183e653
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #9142 +/- ##
==========================================
- Coverage 83.27% 83.23% -0.04%
==========================================
Files 420 420
Lines 34022 34026 +4
==========================================
- Hits 28331 28323 -8
- Misses 4262 4271 +9
- Partials 1429 1432 +3
🚀 New features to boost your workflow:
|
4381232 to
056e67a
Compare
| close(updates) | ||
| if len(updates) != 0 { | ||
| t.Fatalf("expected exactly 2 updates in channel") | ||
| } |
There was a problem hiding this comment.
I don't quite understand what this block of code is for.
There was a problem hiding this comment.
The intent was to catch if the code calls the callback >1x per file modification.
There was a problem hiding this comment.
Please replace this block of code with the following
If we make updates to be a channel of size 1 as I recommended above, you could instead do the following here:
sCtx, sCancel := context.WithTimeout(ctx, 100 * time.Millisecond)
defer sCancel()
select {
case <-updates:
t.Fatal("OnPolicyUpdate callback invoked more times than expected")
case <-sCtx.Done():
}The above approach has the added benefit of waiting for a short amount of time to ensure that no additional callback invocations happen.
dce75de to
a7534e0
Compare
|
@hnefatl : Please don't mark review comment threads as resolved. It is the responsibility of the reviewer to do that. If the author marks them as "resolved", the reviewer would have to "unresolve" each of those to verify if the comments were addressed satisfactorily. Thanks for understanding. |
easwars
left a comment
There was a problem hiding this comment.
LGTM, modulo minor nits.
| // options. | ||
| func NewFileWatcherWithOptions(options FileWatcherOptions) (*FileWatcherInterceptor, error) { | ||
| if options.PolicyFile == "" { | ||
| return nil, fmt.Errorf("authorization policy file path is empty") |
There was a problem hiding this comment.
Nit: While you are here, could you please add an "authz: " prefix to all error returned by this package from its exported functions? I can only see three of them now.
There was a problem hiding this comment.
Done+updated tests in a separate commit (iiuc they'll be merged into a single commit on main? but helps keep things distinct during review).
I kept the existing "exact error matching" rather than switching to e.g. regex matching errors because it was less disruptive, but lmk if you'd prefer switching to a fuzzier match.
|
|
||
| select { | ||
| case <-ctx.Done(): | ||
| t.Fatalf("timeout waiting for policy update") |
There was a problem hiding this comment.
Nit: s/timeout/Timeout
The non-capitalization of error messages does not apply to test error strings and logs. See: https://google.github.io/styleguide/go/decisions#error-strings
Here and elsewhere in this test. Thanks.
| ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout) | ||
| defer cancel() | ||
|
|
||
| updates := make(chan string, 10) |
There was a problem hiding this comment.
Please make this a channel of size 1.
| close(updates) | ||
| if len(updates) != 0 { | ||
| t.Fatalf("expected exactly 2 updates in channel") | ||
| } |
There was a problem hiding this comment.
Please replace this block of code with the following
If we make updates to be a channel of size 1 as I recommended above, you could instead do the following here:
sCtx, sCancel := context.WithTimeout(ctx, 100 * time.Millisecond)
defer sCancel()
select {
case <-updates:
t.Fatal("OnPolicyUpdate callback invoked more times than expected")
case <-sCtx.Done():
}The above approach has the added benefit of waiting for a short amount of time to ensure that no additional callback invocations happen.
4d43a3a to
191ecae
Compare
easwars
left a comment
There was a problem hiding this comment.
Apologies for the delay in the review.
LGTM, modulo minor nits
| file := createTmpPolicyFile(t, "onpolicyupdate", []byte(content)) | ||
| i, err := authz.NewFileWatcherWithOptions(authz.FileWatcherOptions{PolicyFile: file, RefreshDuration: time.Millisecond, OnPolicyUpdate: onPolicyUpdate}) | ||
| if err != nil { | ||
| t.Fatalf("NewFileWatcherWithCallback() returned err: %v", err) |
There was a problem hiding this comment.
Nit: s/NewFileWatcherWithCallback/NewFileWatcherWithOptions
|
|
||
| content := `{"name": "foo1", "allow_rules":[{"name":"bar"}]}` | ||
| file := createTmpPolicyFile(t, "onpolicyupdate", []byte(content)) | ||
| i, err := authz.NewFileWatcherWithOptions(authz.FileWatcherOptions{PolicyFile: file, RefreshDuration: time.Millisecond, OnPolicyUpdate: onPolicyUpdate}) |
There was a problem hiding this comment.
Nit: Please make a local var opts to hold the options and pass it to authz.NewFileWatcherWithOptions. That way, the options can be printed in the following t.Fatalf. See: https://google.github.io/styleguide/go/decisions#identify-the-input
|
|
||
| select { | ||
| case <-ctx.Done(): | ||
| t.Fatalf("timeout waiting for policy update") |
|
/gemini review |
|
Moving to @mbissa for second set of eyes. I had some minor nits in my last pass, but looks mostly good to me. |
There was a problem hiding this comment.
Code Review
This pull request prefixes authorization error messages with "authz: " and introduces FileWatcherOptions to support an OnPolicyUpdate callback when the authorization policy is updated. Feedback on these changes includes replacing t.Context() with context.Background() in tests to maintain compatibility with older Go versions, documenting that the OnPolicyUpdate callback runs synchronously to prevent blocking the background refresh goroutine, and increasing the test's refresh duration from 1ms to 20ms to avoid high CPU usage and test flakiness.
| } | ||
|
|
||
| func (s) TestOnPolicyUpdate(t *testing.T) { | ||
| ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout) |
There was a problem hiding this comment.
Using t.Context() requires Go 1.24 or later. Since grpc-go maintains compatibility with older Go versions (such as Go 1.22 and 1.23), using t.Context() will break compilation on those versions.
Please use context.Background() instead to ensure backward compatibility.
| ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout) | |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
|
|
||
| content := `{"name": "foo1", "allow_rules":[{"name":"bar"}]}` | ||
| file := createTmpPolicyFile(t, "onpolicyupdate", []byte(content)) | ||
| i, err := authz.NewFileWatcherWithOptions(authz.FileWatcherOptions{PolicyFile: file, RefreshDuration: time.Millisecond, OnPolicyUpdate: onPolicyUpdate}) |
There was a problem hiding this comment.
Using a RefreshDuration of time.Millisecond (1ms) is extremely aggressive and causes the background goroutine to poll the file 1000 times per second. This can lead to high CPU usage and test flakiness, especially on resource-constrained CI environments.
Consider increasing this to a more reasonable value like 20 * time.Millisecond.
| i, err := authz.NewFileWatcherWithOptions(authz.FileWatcherOptions{PolicyFile: file, RefreshDuration: time.Millisecond, OnPolicyUpdate: onPolicyUpdate}) | |
| i, err := authz.NewFileWatcherWithOptions(authz.FileWatcherOptions{PolicyFile: file, RefreshDuration: 20 * time.Millisecond, OnPolicyUpdate: onPolicyUpdate}) |
Signed-off-by: Keith Collister <kcollister@google.com>
|
Addressed comments from Easwar+Gemini. |
…(#83) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `v1.82.0` → `v1.83.1` |  |  | --- ### Release Notes <details> <summary>grpc/grpc-go (google.golang.org/grpc)</summary> ### [`v1.83.1`](https://github.com/grpc/grpc-go/releases/tag/v1.83.1): Release 1.83.1 [Compare Source](grpc/grpc-go@v1.83.0...v1.83.1) ### Security - xds/rbac: Fix a bug where nested `Principal` or `Permission` rules with `:scheme` or `grpc-` prefixed header matchers were not rejected, which could cause DENY rules to fail open. ([#​9258](grpc/grpc-go#9258)) - Special Thanks: [@​nvxbug](https://github.com/nvxbug) - xds/rbac: Fix a bug where the `host` header matcher was not being replaced with `:authority` in nested `Principal` or `Permission` rules. ([#​9258](grpc/grpc-go#9258)) - Special Thanks: [@​nvxbug](https://github.com/nvxbug) - xds/rbac: Fix a bug where a header matcher whose name was not lowercase, such as `X-Role`, matched no header, which could cause DENY rules to fail open. ([#​9332](grpc/grpc-go#9332)) - Special Thanks: [@​alimony](https://github.com/alimony) - xds/rbac: Fix a bug where a `:scheme` or `grpc-` prefixed header matcher was accepted when its name was not lowercase. ([#​9332](grpc/grpc-go#9332)) - Special Thanks: [@​alimony](https://github.com/alimony) - xds/rbac: Fix a bug where a `Host` header matcher was not replaced with `:authority`. ([#​9332](grpc/grpc-go#9332)) - Special Thanks: [@​alimony](https://github.com/alimony) ### Performance - transport: Restrict memory overhead of buffering small data frames. ([#​9331](grpc/grpc-go#9331)) ### [`v1.83.0`](https://github.com/grpc/grpc-go/releases/tag/v1.83.0): Release 1.83.0 [Compare Source](grpc/grpc-go@v1.82.1...v1.83.0) ### Security - server: Stop reading from connections when flooded by HTTP/2 frames to mitigate resource exhaustion. The default value for this limit is 100 frames, excluding DATA and HEADERS, and may be changed by setting environment variable `GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT`. - xds/rbac: Support `Metadata` and `RequestedServerName` permissions matcher fields. If present in a DENY rule, previously these would be ignored and fail-open. - xds/rbac: Fix panic when parsing unsupported fields in `NotRule`/`NotId` permissions. - xds/rbac: Support the deprecated `source_ip` principal identifier by treating it as equivalent to `direct_remote_ip`. - xds: Fix panic when parsing route header matchers configured with empty `exact_match`, `prefix_match`, or `suffix_match` strings. ([#​9223](grpc/grpc-go#9223)) ### New Features - xds/googlec2p: Enable DirectPath over Interconnect support for on-premises clients via the `force-xds` target URI query parameter. ([#​9133](grpc/grpc-go#9133)) - xds: Enable xDS configuration to control which fields get propagated from ORCA backend metric reports to LRS load reports. ([#​9145](grpc/grpc-go#9145)) - authz: Add `OnPolicyUpdate` callback to `FileWatcherOptions` to notify when an authz policy is loaded or updated. ([#​9142](grpc/grpc-go#9142)) - Special Thanks: [@​hnefatl](https://github.com/hnefatl) - xds: Add support for the GCP Authentication HTTP Filter, which automatically fetches and attaches GCP Service Account Identity JWT tokens to outgoing RPCs. - This feature can be enabled by setting environment variable `GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER=true`. ([#​9119](grpc/grpc-go#9119)) - xds: Add support for xDS-based HTTP CONNECT proxies. - This feature can be enabled by setting environment variable `GRPC_EXPERIMENTAL_XDS_HTTP_CONNECT=true`. ([#​9151](grpc/grpc-go#9151)) - xds: Add support for `contains_match` in route header matchers. ([#​9223](grpc/grpc-go#9223)) ### Bug Fixes - credentials/alts: Fix panic when processing malformed frames by validating that the message frame length exceeds the message type field size. ([#​9197](grpc/grpc-go#9197)) - grpc: Fix compilation on Plan 9 targets (`GOOS=plan9`), broken since v1.81.0. ([#​9255](grpc/grpc-go#9255)) - Special Thanks: [@​Yusufihsangorgel](https://github.com/Yusufihsangorgel) ### [`v1.82.1`](https://github.com/grpc/grpc-go/releases/tag/v1.82.1): Release 1.82.1 [Compare Source](grpc/grpc-go@v1.82.0...v1.82.1) ### Security - server: Stop reading from the connection when flooded by HTTP/2 frames. The default value for this limit is 100 frames, excluding DATA and HEADERS, and may be changed by setting environment variable `GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT`. - xds/rbac: Support `Metadata` and `RequestedServerName` permissions matcher fields. If present in a DENY rule, previously these would be ignored and fail-open. - xds/rbac: Fix panic when parsing unsupported fields in `NotRule`/`NotId` permissions. - xds/rbac: Support the deprecated `source_ip` principal identifier by treating it as equivalent to `direct_remote_ip`. </details> --- ### Configuration 📅 **Schedule**: (in timezone Europe/Paris) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjMxLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbInR5cGUvbWlub3IiXX0=--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/runner-k8s-plugin/pulls/83
Small additional capability, so user code can tell when a new policy has been loaded.
Our current usecase is updating an opentelemetry metric to reflect the policy version (~= file mtime). We could just run a goroutine polling the file separately, but then there's no guarantee that the authz policy has actually been loaded - it could have failed parsing, or the process could be CPU-starved, etc.
RELEASE NOTES: