Skip to content

feat(policy): Add UnsafeUpdateKey implementation#3731

Draft
c-r33d wants to merge 8 commits into
mainfrom
unsafe-update-key-impl
Draft

feat(policy): Add UnsafeUpdateKey implementation#3731
c-r33d wants to merge 8 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:

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 561427fb-a4c0-4330-a1e4-a5ac1cfcbd0f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 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 on lines +592 to +612
func validateUnsafeUpdateKey(existing *policy.KasKey, r *unsafe.UnsafeUpdateKeyRequest) (unsafeUpdateKeyParams, error) {
existingMode := existing.GetKey().GetKeyMode()
params := unsafeUpdateKeyParams{
ID: r.GetId(),
ProviderConfigID: pgtypeUUID(r.GetProviderConfigId()),
}

if existingMode != policy.KeyMode_KEY_MODE_PUBLIC_KEY_ONLY && existingMode != policy.KeyMode_KEY_MODE_REMOTE {
return params, ErrUnsafeUpdateKeyExistingModeUnsupported
}

if r.GetKeyMode() == policy.KeyMode_KEY_MODE_UNSPECIFIED && existingMode != policy.KeyMode_KEY_MODE_REMOTE {
return params, ErrUnsafeUpdateKeyProviderConfigExistingMode
}

if r.GetKeyMode() != policy.KeyMode_KEY_MODE_UNSPECIFIED {
params.KeyMode = pgtypeInt4(int32(r.GetKeyMode()), true)
}

return params, nil
}

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

The database layer should robustly validate the provider_config_id based on the target key mode rather than relying solely on the proto-level CEL validation. This prevents inconsistent database states (e.g., a REMOTE key with an empty/invalid provider_config_id, or a PUBLIC_KEY_ONLY key with a provider_config_id set) if the gRPC validation is bypassed or when called programmatically.

func validateUnsafeUpdateKey(existing *policy.KasKey, r *unsafe.UnsafeUpdateKeyRequest) (unsafeUpdateKeyParams, error) {
	existingMode := existing.GetKey().GetKeyMode()
	params := unsafeUpdateKeyParams{
		ID:               r.GetId(),
		ProviderConfigID: pgtypeUUID(r.GetProviderConfigId()),
	}

	if existingMode != policy.KeyMode_KEY_MODE_PUBLIC_KEY_ONLY && existingMode != policy.KeyMode_KEY_MODE_REMOTE {
		return params, ErrUnsafeUpdateKeyExistingModeUnsupported
	}

	if r.GetKeyMode() == policy.KeyMode_KEY_MODE_UNSPECIFIED && existingMode != policy.KeyMode_KEY_MODE_REMOTE {
		return params, ErrUnsafeUpdateKeyProviderConfigExistingMode
	}

	targetMode := r.GetKeyMode()
	if targetMode == policy.KeyMode_KEY_MODE_UNSPECIFIED {
		targetMode = existingMode
	}

	if targetMode == policy.KeyMode_KEY_MODE_REMOTE {
		if r.GetProviderConfigId() == "" || !params.ProviderConfigID.Valid {
			return params, db.ErrUUIDInvalid
		}
	} else if targetMode == policy.KeyMode_KEY_MODE_PUBLIC_KEY_ONLY {
		if r.GetProviderConfigId() != "" {
			return params, errors.Join(db.ErrSelectIdentifierInvalid, fmt.Errorf("provider_config_id must be empty for PUBLIC_KEY_ONLY mode"))
		}
	}

	if r.GetKeyMode() != policy.KeyMode_KEY_MODE_UNSPECIFIED {
		params.KeyMode = pgtypeInt4(int32(r.GetKeyMode()), true)
	}

	return params, nil
}

Comment thread service/policy/db/key_access_server_registry.go
Comment on lines +3 to +10
import (
"testing"

"github.com/jackc/pgx/v5/pgtype"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/unsafe"
"github.com/stretchr/testify/require"
)

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

Import the service/pkg/db package to allow using db.ErrUUIDInvalid in the new test case verifying invalid provider_config_id validation.

Suggested change
import (
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/unsafe"
"github.com/stretchr/testify/require"
)
import (
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/opentdf/platform/protocol/go/policy"
"github.com/opentdf/platform/protocol/go/policy/unsafe"
"github.com/opentdf/platform/service/pkg/db"
"github.com/stretchr/testify/require"
)

Comment on lines +66 to +73
{
name: "existing config root key rejected before request validation",
existingMode: policy.KeyMode_KEY_MODE_CONFIG_ROOT_KEY,
requestMode: policy.KeyMode_KEY_MODE_REMOTE,
providerConfig: unsafeUpdateKeyTestUUID,
wantErr: ErrUnsafeUpdateKeyExistingModeUnsupported,
},
}

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

Add a test case to verify that an invalid provider_config_id is correctly rejected when the target key mode is KEY_MODE_REMOTE.

		{
			name:           "existing config root key rejected before request validation",
			existingMode:   policy.KeyMode_KEY_MODE_CONFIG_ROOT_KEY,
			requestMode:    policy.KeyMode_KEY_MODE_REMOTE,
			providerConfig: unsafeUpdateKeyTestUUID,
			wantErr:        ErrUnsafeUpdateKeyExistingModeUnsupported,
		},
		{
			name:           "invalid provider config id for remote mode",
			existingMode:   policy.KeyMode_KEY_MODE_REMOTE,
			requestMode:    policy.KeyMode_KEY_MODE_REMOTE,
			providerConfig: "invalid-uuid",
			wantErr:        db.ErrUUIDInvalid,
		},
	}

Comment on lines +453 to +456
func unsafeUpdateKeyAuditValue(kasKey *policy.KasKey) *policy.KasKey {
if kasKey.GetKey() == nil {
return nil
}

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

Add an explicit nil check for kasKey to ensure robust defensive programming and prevent potential nil pointer dereference panics.

Suggested change
func unsafeUpdateKeyAuditValue(kasKey *policy.KasKey) *policy.KasKey {
if kasKey.GetKey() == nil {
return nil
}
func unsafeUpdateKeyAuditValue(kasKey *policy.KasKey) *policy.KasKey {
if kasKey == nil || kasKey.GetKey() == nil {
return nil
}

Comment on lines +468 to +471
func unsafeUpdateKeyAuditProviderConfig(providerConfig *policy.KeyProviderConfig) *policy.KeyProviderConfig {
if providerConfig == nil {
return &policy.KeyProviderConfig{}
}

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

When providerConfig is nil, return nil instead of an empty &policy.KeyProviderConfig{}. Returning an empty struct results in empty objects in the serialized audit logs (e.g., "providerConfig": {}), which is noisy and misleading.

Suggested change
func unsafeUpdateKeyAuditProviderConfig(providerConfig *policy.KeyProviderConfig) *policy.KeyProviderConfig {
if providerConfig == nil {
return &policy.KeyProviderConfig{}
}
func unsafeUpdateKeyAuditProviderConfig(providerConfig *policy.KeyProviderConfig) *policy.KeyProviderConfig {
if providerConfig == nil {
return nil
}

@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

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

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

See the workflow run for details.

@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

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.

1 participant