Skip to content

authz: add onPolicyUpdate callback to authz file watcher - #9142

Merged
mbissa merged 1 commit into
grpc:masterfrom
hnefatl:onpolicyupdate
Jul 8, 2026
Merged

authz: add onPolicyUpdate callback to authz file watcher#9142
mbissa merged 1 commit into
grpc:masterfrom
hnefatl:onpolicyupdate

Conversation

@hnefatl

@hnefatl hnefatl commented May 27, 2026

Copy link
Copy Markdown
Contributor

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:

  • TBD

@linux-foundation-easycla

linux-foundation-easycla Bot commented May 27, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: hnefatl / name: Keith Collister (bc68034)

@hnefatl
hnefatl force-pushed the onpolicyupdate branch 3 times, most recently from bc68034 to 183e653 Compare May 27, 2026 14:23
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.57143% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.23%. Comparing base (5013974) to head (1bb5b67).

Files with missing lines Patch % Lines
authz/grpc_authz_server_interceptors.go 78.57% 2 Missing and 1 partial ⚠️
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     
Files with missing lines Coverage Δ
authz/grpc_authz_server_interceptors.go 83.33% <78.57%> (+0.98%) ⬆️

... and 30 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hnefatl
hnefatl marked this pull request as ready for review May 27, 2026 14:29
Comment thread authz/grpc_authz_server_interceptors.go Outdated
@easwars easwars added Type: Feature New features or improvements in behavior Area: Auth Includes regular credentials API and implementation. Also includes advancedtls, authz, rbac etc. labels May 27, 2026
@easwars easwars added this to the 1.82 Release milestone May 27, 2026
@hnefatl
hnefatl force-pushed the onpolicyupdate branch 4 times, most recently from 4381232 to 056e67a Compare May 28, 2026 09:27
@easwars easwars assigned easwars and unassigned hnefatl Jun 3, 2026
@mbissa mbissa modified the milestones: 1.82 Release, 1.83 Release Jun 5, 2026
Comment thread authz/grpc_authz_server_interceptors.go Outdated
Comment thread authz/grpc_authz_server_interceptors.go Outdated
Comment thread authz/grpc_authz_server_interceptors.go Outdated
Comment thread authz/grpc_authz_server_interceptors.go Outdated
Comment thread authz/grpc_authz_server_interceptors.go Outdated
Comment thread authz/grpc_authz_server_interceptors_test.go Outdated
Comment thread authz/grpc_authz_server_interceptors_test.go Outdated
Comment thread authz/grpc_authz_server_interceptors_test.go Outdated
Comment thread authz/grpc_authz_server_interceptors_test.go Outdated
Comment on lines +148 to +151
close(updates)
if len(updates) != 0 {
t.Fatalf("expected exactly 2 updates in channel")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand what this block of code is for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent was to catch if the code calls the callback >1x per file modification.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@easwars easwars assigned hnefatl and unassigned easwars Jun 5, 2026
@hnefatl
hnefatl force-pushed the onpolicyupdate branch 3 times, most recently from dce75de to a7534e0 Compare June 10, 2026 14:25
@hnefatl
hnefatl requested a review from easwars June 10, 2026 14:26
@easwars

easwars commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

@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 easwars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modulo minor nits.

Comment thread authz/grpc_authz_server_interceptors.go Outdated
// options.
func NewFileWatcherWithOptions(options FileWatcherOptions) (*FileWatcherInterceptor, error) {
if options.PolicyFile == "" {
return nil, fmt.Errorf("authorization policy file path is empty")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout)
defer cancel()

updates := make(chan string, 10)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this a channel of size 1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines +148 to +151
close(updates)
if len(updates) != 0 {
t.Fatalf("expected exactly 2 updates in channel")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@easwars easwars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the delay in the review.

LGTM, modulo minor nits

Comment thread authz/grpc_authz_server_interceptors.go
Comment thread authz/grpc_authz_server_interceptors.go
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: s/timeout/Timeout

@easwars

easwars commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@easwars

easwars commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Moving to @mbissa for second set of eyes. I had some minor nits in my last pass, but looks mostly good to me.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
ctx, cancel := context.WithTimeout(t.Context(), defaultTestTimeout)
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)

Comment thread authz/grpc_authz_server_interceptors.go

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})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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>
@hnefatl

hnefatl commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed comments from Easwar+Gemini.

@mbissa mbissa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mbissa
mbissa merged commit 2a112a8 into grpc:master Jul 8, 2026
14 checks passed
eleboucher pushed a commit to eleboucher/runner-k8s-plugin that referenced this pull request Aug 19, 2026
…(#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` | ![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fgrpc/v1.83.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fgrpc/v1.82.0/v1.83.1?slim=true) |

---

### 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. ([#&#8203;9258](grpc/grpc-go#9258))
  - Special Thanks: [@&#8203;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. ([#&#8203;9258](grpc/grpc-go#9258))
  - Special Thanks: [@&#8203;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. ([#&#8203;9332](grpc/grpc-go#9332))
  - Special Thanks: [@&#8203;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. ([#&#8203;9332](grpc/grpc-go#9332))
  - Special Thanks: [@&#8203;alimony](https://github.com/alimony)
- xds/rbac: Fix a bug where a `Host` header matcher was not replaced with `:authority`. ([#&#8203;9332](grpc/grpc-go#9332))
  - Special Thanks: [@&#8203;alimony](https://github.com/alimony)

### Performance

- transport: Restrict memory overhead of buffering small data frames. ([#&#8203;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. ([#&#8203;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. ([#&#8203;9133](grpc/grpc-go#9133))
- xds: Enable xDS configuration to control which fields get propagated from ORCA backend metric reports to LRS load reports. ([#&#8203;9145](grpc/grpc-go#9145))
- authz: Add `OnPolicyUpdate` callback to `FileWatcherOptions` to notify when an authz policy is loaded or updated. ([#&#8203;9142](grpc/grpc-go#9142))
  - Special Thanks: [@&#8203;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`. ([#&#8203;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`. ([#&#8203;9151](grpc/grpc-go#9151))
- xds: Add support for `contains_match` in route header matchers. ([#&#8203;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. ([#&#8203;9197](grpc/grpc-go#9197))
- grpc: Fix compilation on Plan 9 targets (`GOOS=plan9`), broken since v1.81.0. ([#&#8203;9255](grpc/grpc-go#9255))
  - Special Thanks: [@&#8203;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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Auth Includes regular credentials API and implementation. Also includes advancedtls, authz, rbac etc. Type: Feature New features or improvements in behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants