Skip to content

ADR: Enhancement to the KeyAccessServer Table #1485

@jrschumacher

Description

@jrschumacher

Enhance Key Access Server Table

Context and Problem Statement

Currently, the Key Access Server (KAS) stores a jsonb representation of the public_key. This design presents a limitation, as it implies that a Key Access Server can have only a single public key associated with it. However, this is not the case.

In our proto definitions, we define the PublicKey field within the KeyAccessServer proto message:

PublicKey public_key = 3;

The PublicKey message can take on one of the following forms:

message PublicKey {
// Deprecated
reserved "local";
reserved 2;
oneof public_key {
// kas public key url - optional since can also be retrieved via public key
string remote = 1 [(buf.validate.field).cel = {
id: "uri_format"
message: "URI must be a valid URL (e.g., 'https://demo.com/') followed by additional segments. Each segment must start and end with an alphanumeric character, can contain hyphens, alphanumeric characters, and slashes."
expression: "this.matches('^https://[a-zA-Z0-9]([a-zA-Z0-9\\\\-]{0,61}[a-zA-Z0-9])?(\\\\.[a-zA-Z0-9]([a-zA-Z0-9\\\\-]{0,61}[a-zA-Z0-9])?)*(/.*)?$')"
}];
// public key with additional information. Current preferred version
KasPublicKeySet cached = 3;
}
}

This allows for either storing a reference to a remote public key or defining a public key via a KasPublicKeySet, which is essentially a repeated list of KasPublicKey entries:

message KasPublicKey {
// x509 ASN.1 content in PEM envelope, usually
string pem = 1;
// A unique string identifier for this key
string kid = 2;
// A known algorithm type with any additional parameters encoded.
// To start, these may be `rsa:2048` for encrypting ZTDF files and
// `ec:secp256r1` for nanoTDF, but more formats may be added as needed.
KasPublicKeyAlgEnum alg = 3;
}

Potential Issues:

  1. Key Rotation Confusion: As an admin, I could list multiple RSA keys, including old rotated keys, which might create confusion about which key is active.
  2. Lack of Key Selection Mechanism: There is currently no way for our SDK to identify which key should be used as the correct or active key.

Additionally, there is a mismatch between how we assign the KAS to a namespace, attribute, or attribute value. This assignment would work if the KAS were intended to operate with only a single key at a time. However, this does not align with how KAS was originally designed.

Proposed Solution:

Instead of associating a KAS directly with a namespace, attribute, or attribute value, we should use Grants to map a specific Key to the policy resources. This would better reflect the original design intent and allow for more flexible future key management.

Decision Drivers

  • Support key rotations
  • Ensure strict values
  • Allow Key to namespace, attribute, or value mapping

Considered Options

  • Store multiple keys within the public_key jsonb typed column
  • Create an auxiliary table to store the keys with a many:1 relationship with the KeyAccessServer table

Decision Outcome

Chosen option: "{title of option}"

Store multiple keys within the public_key jsonb typed column

  • 🟢 Existing table structure and no migration needed
  • 🔴 With JSONB the data does not have a schema to conform to and data structure migrations are hard
  • 🔴 No indication from a high-level which key is primary without decoding the JSONB
  • 🔴 Row needs to be updated "frequently" to capture key rotations and identify the current key
  • 🔴 After many key rotations the resulting data is quite large and filtering needs to be added to the service code

Create an auxiliary table to store the keys

erDiagram

    %% KeyAccessServer ||--|| PublicKey: "has current key"
    PublicKey }|--|| KeyAccessServer: "belongs to"

    XPublicKeyMap }|--|| PublicKey: "has many"
	X ||--|| XPublicKeyMap: "has one"

    X {
        uuid         id                      PK
    }

    XPublicKeyMap {
        uuid  X_id FK
        uuid  key_access_server_id FK
    }

	%% moving public_key out
    KeyAccessServer {
        uuid       id                PK
	text       name
        text       uri               UK
        jsonb      metadata
	date	created_on
	date	updated_on
    }

    

    PublicKey {
        uuid        id                      PK 
        boolean     is_active         
        boolean     was_mapped          
        uuid        key_access_server_id    FK
        varchar(36) key_id
        varchar(50) alg                     "algorithm"
        constraint  unique_key              UK  "enforces unique key_id and algorithm per KAS (key_access_server_id, key_id, alg)"
        constraint  unique_active_key       UK  "enforce only one active key per KAS per algorithm"
        text        public_key
        jsonb       metadata
    }

Loading

Updated from #1485 (comment)

  • 🟢 Schema enforced solution to multiple keys
  • 🟢 Ensure future migrations are simpler (not-NoSQL)
  • 🟡 Requires an auto migration to move public keys
  • 🔴 If public key json has many keys, a manual migration is required

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    adrArchitecture Decision Records pertaining to OpenTDF

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions