Skip to content

feat(policy): Add UnsafeUpdateKey implementation - #3731

Merged
c-r33d merged 20 commits into
mainfrom
unsafe-update-key-impl
Jul 22, 2026
Merged

feat(policy): Add UnsafeUpdateKey implementation#3731
c-r33d merged 20 commits into
mainfrom
unsafe-update-key-impl

Conversation

@c-r33d

@c-r33d c-r33d commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements UnsafeUpdateKey for KAS registry keys.

Changes

  • Added DB support for unsafe key updates.

  • Supports:

    • public key only -> remote with provider config
    • remote -> public key only, clearing provider config
    • remote provider config replacement
  • Added validation for unsupported existing key modes.

  • Added typed errors for unsafe update validation failures.

  • Added integration coverage in kas_registry_key_test.go.

  • Added focused unit coverage for unsafe key update validation.

Depends on:

Summary by CodeRabbit

  • New Features

    • Added support for updating key modes and provider configurations through the unsafe key update operation.
    • Added validation for supported key transitions, provider configuration requirements, and matching key identifiers.
    • Added audit records showing the original and updated key details.
  • Bug Fixes

    • Improved error responses for invalid updates and missing provider configurations.
    • Prevented unsupported or invalid updates from modifying stored keys.
  • Tests

    • Added comprehensive validation, integration, and error-mapping coverage.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds end-to-end support for unsafe key updates, including request and transition validation, provider-config checks, SQL persistence, audit logging, Connect error mapping, and integration tests for successful and failed transitions.

Changes

Unsafe key update

Layer / File(s) Summary
Validation and status contracts
service/pkg/db/errors.go, service/pkg/db/errors_test.go, service/policy/unsafe/unsafe_test.go, service/policy/db/key_access_server_registry_test.go
Adds unsafe-update sentinel errors, status mappings, request validation tests, key-mode transition tests, and error mapping coverage.
Database unsafe update
service/policy/db/queries/key_access_server_registry.sql, service/policy/db/key_access_server_registry.sql.go, service/policy/db/key_access_server_registry.go
Adds the SQL update operation and database client flow for validating keys, checking provider configurations, updating rows, and reloading the result.
RPC orchestration and audit
service/policy/unsafe/unsafe.go
Implements the unsafe-update RPC transaction, audit value conversion, audit logging, and response construction.
Integration coverage and fixtures
service/integration/kas_registry_key_test.go
Tests key-mode transitions, provider-config changes, failure behavior, persisted results, and cleanup helpers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant UnsafeService
  participant PolicyDBClient
  participant Queries
  Client->>UnsafeService: UnsafeUpdateKey request
  UnsafeService->>PolicyDBClient: Load existing key and update
  PolicyDBClient->>PolicyDBClient: Validate modes and provider config
  PolicyDBClient->>Queries: Execute unsafeUpdateKey
  Queries-->>PolicyDBClient: Return affected rows
  PolicyDBClient-->>UnsafeService: Return updated key
  UnsafeService-->>Client: Return response and audit result
Loading

Possibly related PRs

Suggested reviewers: alkalescent

Poem

I’m a rabbit with keys in my den,
Now unsafe updates work again.
Modes hop, configs align,
Errors report just fine.
Tests guard each burrowed path—
Audit trails record the math.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding the UnsafeUpdateKey implementation in the policy layer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch unsafe-update-key-impl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added comp:db DB component comp:policy Policy Configuration ( attributes, subject mappings, resource mappings, kas registry) comp:sdk A software development kit, including library, for client applications and inter-service communicati docs Documentation size/m labels Jul 7, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces an 'UnsafeUpdateKey' operation to the KAS registry, enabling administrative updates to key modes and provider configurations. This functionality is designed for support scenarios where keys need to be reconfigured without deleting and recreating them, ensuring existing TDF decryptability is maintained where applicable. The changes include full gRPC service definitions, database layer updates, and robust validation to prevent invalid state transitions.

Highlights

  • UnsafeUpdateKey Implementation: Added a new 'UnsafeUpdateKey' RPC to the UnsafeService, allowing for in-place modification of KAS registry keys.
  • Key Mode Transitions: Implemented support for switching between REMOTE and PUBLIC_KEY_ONLY key modes, and updating provider configurations for existing REMOTE keys.
  • Validation and Error Handling: Added strict validation for key mode transitions and introduced typed errors to handle unsupported operations.
  • Testing Coverage: Added comprehensive integration tests in 'kas_registry_key_test.go' and unit tests for validation logic in 'key_access_server_registry_unsafe_test.go'.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: docs/openapi/**/* (1)
    • docs/openapi/policy/unsafe/unsafe.openapi.yaml
  • Ignored by pattern: protocol/**/* (3)
    • protocol/go/policy/unsafe/unsafe.pb.go
    • protocol/go/policy/unsafe/unsafe_grpc.pb.go
    • protocol/go/policy/unsafe/unsafeconnect/unsafe.connect.go
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


A key once set in stone and cold, Now shifts its mode as we are told. With unsafe calls and careful care, We update keys with grace and flair.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@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 introduces an unsafe key update operation (UnsafeUpdateKey) to the policy service, allowing support-only switching between REMOTE and PUBLIC_KEY_ONLY key modes or updating the provider configuration for existing remote keys. The changes span protocol definitions, database-layer implementation, audit logging, and comprehensive integration and unit tests. The code reviewer provided several valuable suggestions to enhance the robustness and cleanliness of the implementation, including strengthening database-level validation of provider_config_id based on the target key mode, adding defensive nil checks to prevent potential panics, returning nil instead of empty structs in audit logs to reduce noise, and adding test cases to verify invalid provider configuration ID validation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread service/policy/db/key_access_server_registry.go
Comment thread service/policy/db/key_access_server_registry.go
Comment thread service/policy/db/key_access_server_registry_test.go
Comment thread service/policy/db/key_access_server_registry_test.go
Comment thread service/policy/unsafe/unsafe.go
Comment thread service/policy/unsafe/unsafe.go
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 206.401029ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 114.232398ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 426.494546ms
Throughput 234.47 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 47.803464599s
Average Latency 476.438324ms
Throughput 104.59 requests/second

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 203.855015ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 110.501836ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 423.725647ms
Throughput 236.00 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 45.081782726s
Average Latency 448.679812ms
Throughput 110.91 requests/second

@c-r33d
c-r33d marked this pull request as ready for review July 21, 2026 14:44
@c-r33d
c-r33d requested review from a team as code owners July 21, 2026 14:44
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 226.354622ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 112.805941ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 408.209186ms
Throughput 244.97 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 49.724833218s
Average Latency 496.203717ms
Throughput 100.55 requests/second

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
service/policy/kasregistry/key_access_server_registry.go (1)

299-324: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Double-StatusifyError wrapping silently downgrades all UpdateKey failures to CodeInternal.

Line 303-304 now statusifies the error inside the RunInTx callback, but line 322-324 (unchanged) statusifies it again outside. StatusifyError's first call wraps the error in connect.NewError(code, errors.New(ErrTextXxx)) — a brand-new error unrelated to the original sentinel (e.g. db.ErrCannotUpdateToUnspecified). connect.Error.Unwrap() returns that fresh inner error, so on the second pass none of the errors.Is(err, ErrXxx) checks in StatusifyError match, and it falls through to the generic connect.CodeInternal fallback — discarding the correctly classified code and message from the first call.

This affects every failure path of the pre-existing UpdateKey RPC (e.g. ErrCannotUpdateToUnspecified should map to CodeInvalidArgument, but will now return CodeInternal). service/policy/unsafe/unsafe.go's new UnsafeUpdateKey shows the correct pattern: statusify once inside the callback, then propagate the error unchanged outside.

🐛 Proposed fix: remove the redundant outer statusify
 	if err != nil {
-		return nil, db.StatusifyError(ctx, s.logger, err, db.ErrTextUpdateFailed, slog.String("key_access_server_keys", req.Msg.GetId()))
+		return nil, err
 	}
 
 	return connect.NewResponse(rsp), nil
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/policy/kasregistry/key_access_server_registry.go` around lines 299 -
324, Remove the redundant outer StatusifyError call after RunInTx in the
UpdateKey flow. Return the transaction error unchanged so the statusified error
produced inside the callback retains its original Connect code and message,
matching the propagation pattern used by UnsafeUpdateKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/policy/db/key_access_server_registry.go`:
- Around line 623-630: In the KeyMode_KEY_MODE_UNSPECIFIED branch of the key
access server registry update logic, validate newProviderConfiguration.Valid
before checking existingMode so an empty provider configuration returns
ErrUnsafeUpdateKeyProviderConfigRequired rather than the existing-mode error.
Preserve the current behavior for valid provider configurations and add coverage
for existingMode PUBLIC_KEY_ONLY, requestMode UNSPECIFIED, and an empty provider
configuration.

---

Outside diff comments:
In `@service/policy/kasregistry/key_access_server_registry.go`:
- Around line 299-324: Remove the redundant outer StatusifyError call after
RunInTx in the UpdateKey flow. Return the transaction error unchanged so the
statusified error produced inside the callback retains its original Connect code
and message, matching the propagation pattern used by UnsafeUpdateKey.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2aa85237-02e8-422e-a405-af339e236edf

📥 Commits

Reviewing files that changed from the base of the PR and between c89b193 and ded9cd8.

📒 Files selected for processing (10)
  • service/integration/kas_registry_key_test.go
  • service/pkg/db/errors.go
  • service/pkg/db/errors_test.go
  • service/policy/db/key_access_server_registry.go
  • service/policy/db/key_access_server_registry.sql.go
  • service/policy/db/key_access_server_registry_test.go
  • service/policy/db/queries/key_access_server_registry.sql
  • service/policy/kasregistry/key_access_server_registry.go
  • service/policy/unsafe/unsafe.go
  • service/policy/unsafe/unsafe_test.go

Comment thread service/policy/db/key_access_server_registry.go
@github-actions

Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 193.999194ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 101.46225ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 433.644931ms
Throughput 230.60 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 49.036764708s
Average Latency 488.581997ms
Throughput 101.96 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 247.828795ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 154.355394ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 398.496623ms
Throughput 250.94 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 45.759325519s
Average Latency 456.137311ms
Throughput 109.27 requests/second

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 217.052323ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 111.298863ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 406.489533ms
Throughput 246.01 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 50.505749286s
Average Latency 502.300858ms
Throughput 99.00 requests/second

@c-r33d
c-r33d requested a review from alkalescent July 22, 2026 16:10
alkalescent
alkalescent previously approved these changes Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 199.782281ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 113.448806ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 410.944278ms
Throughput 243.34 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 53.665745701s
Average Latency 535.198426ms
Throughput 93.17 requests/second

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
service/policy/db/key_access_server_registry.go (1)

561-566: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Compare UUID values, not raw strings.

pgtypeUUID parses request IDs, but UnsafeUpdateKey later compares the stored ID string with the request ID string. Uppercase or case-variant equivalent UUIDs would incorrectly return key ID mismatch; compare parsed UUID bytes or canonical string forms instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/policy/db/key_access_server_registry.go` around lines 561 - 566,
Update UnsafeUpdateKey’s key-ID validation to compare UUID values rather than
raw request and stored ID strings. Parse both IDs using the existing pgtypeUUID
representation and compare their UUID bytes or canonical forms, while preserving
the invalid-UUID error and mismatch error behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@service/policy/db/key_access_server_registry.go`:
- Around line 561-566: Update UnsafeUpdateKey’s key-ID validation to compare
UUID values rather than raw request and stored ID strings. Parse both IDs using
the existing pgtypeUUID representation and compare their UUID bytes or canonical
forms, while preserving the invalid-UUID error and mismatch error behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5e7aa58d-4f6f-42cf-8cc7-ab9ac108a520

📥 Commits

Reviewing files that changed from the base of the PR and between 654c6f1 and 40a5c3b.

📒 Files selected for processing (2)
  • service/pkg/db/errors.go
  • service/policy/db/key_access_server_registry.go

@github-actions

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 228.022611ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 116.860472ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 422.783931ms
Throughput 236.53 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 53.130353565s
Average Latency 529.402555ms
Throughput 94.11 requests/second

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

@c-r33d
c-r33d added this pull request to the merge queue Jul 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 22, 2026
@c-r33d
c-r33d added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit cd47518 Jul 22, 2026
46 checks passed
@c-r33d
c-r33d deleted the unsafe-update-key-impl branch July 22, 2026 19:45
khvirtru pushed a commit to khvirtru/platform that referenced this pull request Jul 24, 2026
## Summary

Adds CLI support for unsafely updating KAS registry keys through the new
`UnsafeUpdateKey` service RPC.

  This introduces:

  - `otdfctl policy kas-registry key unsafe update`
  - support for changing eligible keys between `remote` and `public_key`
  - support for updating the provider config on an existing `remote` key
- unsafe confirmation behavior consistent with other destructive/unsafe
key commands
  - CLI docs and examples for the new command
- E2E BATS coverage for successful updates and service-originated
validation errors

  ## CLI Behavior

  The new command accepts:

  - `--id`: system key ID to update
- `--mode`: target key mode, limited client-side to `remote` or
`public_key`
- `--provider-config-id`: provider config ID for `remote` mode or remote
provider config updates
  - `--force`: bypasses interactive unsafe confirmation

Request validation for provider config combinations is intentionally
handled by the service. The CLI now validates only CLI-level concerns
like required
  flags, UUID parsing, and unsupported mode strings.

  ## Testing

  Updated `otdfctl/e2e/kas-keys.bats` to cover:

  - remote to public key mode
  - public key to remote mode
  - remote provider config-only update
  - invalid provider config UUID
  - missing ID
  - service errors for missing provider config
  - service errors for provider config not allowed
  - service errors for unsupported existing key mode

  ## Depends On

  - opentdf#3728: `feat(policy): Add UnsafeUpdateKey rpc.`
  - opentdf#3731: `feat(policy): Add UnsafeUpdateKey implementation`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added an unsafe update command for KAS keys.
  * Supports switching between `remote` and `public_key` modes.
  * Supports updating provider configuration for existing remote keys.
  * Added confirmation prompts and result details after updates.

* **Bug Fixes**
* Added validation for unsupported modes, missing identifiers, and
invalid provider configurations.

* **Documentation**
  * Added usage guidance and safety warnings for the new command.
  * Updated the unsafe delete command examples.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Chris Reed <creed@virtru.com>
khvirtru pushed a commit to khvirtru/platform that referenced this pull request Jul 24, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.21.0](opentdf/platform@service/v0.20.0...service/v0.21.0)
(2026-07-22)


### ⚠ BREAKING CHANGES

* **policy:** GetPC does not respect manager field.
([opentdf#3752](opentdf#3752))

### Features

* **core:** Update fixtures
([opentdf#3766](opentdf#3766))
([9ac1640](opentdf@9ac1640))
* **policy:** add entitleable namespace field and narrow-read-API doc
corrections ([opentdf#3727](opentdf#3727))
([0fc2f01](opentdf@0fc2f01))
* **policy:** Add name and manager lookup
([opentdf#3753](opentdf#3753))
([bfbc65d](opentdf@bfbc65d))
* **policy:** Add UnsafeUpdateKey implementation
([opentdf#3731](opentdf#3731))
([cd47518](opentdf@cd47518))
* **policy:** Add UnsafeUpdateKey rpc.
([opentdf#3728](opentdf#3728))
([c89b193](opentdf@c89b193))
* **policy:** create subject mappings with attribute values
([opentdf#3741](opentdf#3741))
([718320b](opentdf@718320b))
* **policy:** deprecate GetAttributeValuesByFqns in favor of narrow read
APIs ([opentdf#3745](opentdf#3745))
([baba709](opentdf@baba709))
* **policy:** dynamic attribute value entitlement mappings
([opentdf#3568](opentdf#3568))
([21e95e0](opentdf@21e95e0))
* **policy:** populate entitleable definition namespace
([opentdf#3737](opentdf#3737))
([3f32839](opentdf@3f32839))


### Bug Fixes

* **authz:** use standard Keycloak token exchange
([opentdf#3754](opentdf#3754))
([de9ae0d](opentdf@de9ae0d))
* **core:** Pass the platform logger to the authz role provider factory
([opentdf#3732](opentdf#3732))
([9c32554](opentdf@9c32554))
* **core:** prevent OpenTelemetry resource schema conflicts
([opentdf#3769](opentdf#3769))
([4a0c41c](opentdf@4a0c41c))
* **deps:** bump github.com/opentdf/platform/protocol/go from 0.38.0 to
0.39.0 in /service
([opentdf#3764](opentdf#3764))
([8d430f5](opentdf@8d430f5))
* **ers:** coerce attempted_strategies []string to []interface{} for
structpb ([opentdf#3645](opentdf#3645))
([71d6e90](opentdf@71d6e90))
* **policy:** GetPC does not respect manager field.
([opentdf#3752](opentdf#3752))
([0d3c0a0](opentdf@0d3c0a0))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: opentdf-automation[bot] <149537512+opentdf-automation[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:db DB component comp:policy Policy Configuration ( attributes, subject mappings, resource mappings, kas registry) comp:sdk A software development kit, including library, for client applications and inter-service communicati docs Documentation size/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants