Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(policy): adds new public keys table #1836

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

strantalis
Copy link
Member

@strantalis strantalis commented Jan 6, 2025

Proposed Changes

Implements the following ADR as discussed here #1485.

  • Adds new public_keys table along with mappings pivot tables for attribute values, definitions and namespaces.
  • Adds new CRUD RPC's to manage public keys and their associations
  • Returns new keys field on attribute values, definitions and namespaces
erDiagram

    key_access_server {
        uuid       id                PK
        varchar    uri               UK
        varchar    name              UK "new optional name column"
        jsonb      public_key
        jsonb      metadata
    }

    public_keys {
        uuid        id                      PK 
        boolean     is_active         
        boolean     was_used          
        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
    }

    attribute_namespace_public_key_map {
        uuid namespace_id  FK
        uuid public_key_id FK
    }

    attribute_definition_public_key_map {
        uuid attribute_definition_id FK
        uuid public_key_id           FK
    }

    attribute_value_public_key_map {
        uuid attribute_value_id FK
        uuid public_key_id      FK
    }

    key_access_server 1 -- 1+ public_keys : "has"
    public_keys 1 -- 1+ attribute_namespace_public_key_map : "maps"
    public_keys 1 -- 1+ attribute_definition_public_key_map : "maps"
    public_keys 1 -- 1+ attribute_value_public_key_map : "maps"
Loading

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

@strantalis strantalis changed the title Keys table feat(policy): adds new public keys table Jan 6, 2025
message Key {
string id = 1;

bool is_active = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be better to use google.protobuf.BoolValuehere so you can differentiate between set and false, set and true, and unset? https://protobuf.dev/reference/protobuf/google.protobuf/#bool-value This is particularly useful when marshaling/unmarshaling to JSON as an unset field value will become false (Go zero value) without this rather than skipped in the JSON.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same with was_used?

@@ -262,6 +307,21 @@ message KeyAccessServer {
common.Metadata metadata = 100;
}

message Key {
string id = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
string id = 1;
// the database record ID, not the key ID (`kid`)
string id = 1;

Should we add a comment that this is not the kid but the row ID in the database?

message KasPublicKeySet {
repeated KasPublicKey keys = 1;
}
message KasPublicKeySet { repeated KasPublicKey keys = 1; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at this makes me wonder - do we want to get rid of the concept of a public key set alongside the deprecation of grants? If so, can we document that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah that should go away. I believe I only leveraged KasPublicKey. I will go through and add deprecation comments to that and kas grants.

"additional segments. Each segment must start and end with an "
"alphanumeric character, can contain hyphens, alphanumeric "
"characters, and slashes.",
expression : "this.matches('^https?://"
Copy link
Contributor

Choose a reason for hiding this comment

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

Was splitting up the regex like this a requirement from a new linter? I am not sure the formatting improvement is worth the readability impact to the regex. 🤔

Copy link
Member Author

@strantalis strantalis Jan 7, 2025

Choose a reason for hiding this comment

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

I must have a formatter locally that did this.

@@ -186,6 +186,19 @@ message UnsafeDeleteAttributeValueResponse {
policy.Value value = 1;
}

// WARNING!!
message UnsafeDeletePublicKeyRequest {
Copy link
Contributor

Choose a reason for hiding this comment

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

If delete is unsafe, is update also unsafe?

Copy link
Member Author

Choose a reason for hiding this comment

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

Update only allows modification if is_active. Not sure if thats an unsafe action though.

message UpdateKeyRequest {
// Required
string id = 1;
bool active = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the supported update functionality strictly deactivation?

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make the paths clearer between deactivation, reactivation, and a true update (change of KID, alg, or PEM) even if those are all unsafe?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think adding deactivation and reactivation makes sense.

@cassandrabailey293 @jp-ayyappan When managing public keys should we allow modification of that resource or should we force an admin to create a new key so they can retain it for audit purposes in the database.

@@ -1267,6 +1267,18 @@ func (s *AttributesSuite) Test_RemoveKeyAccessServerFromAttribute_Returns_Succes
s.Equal(aKas, resp)
}

func (s *AttributesSuite) Test_GetAttribute_Returns_Only_Active_PublicKeys() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see why this makes sense, but I wonder how you're envisioning policy administration of these associations once created. For example, if I'm a CLI user getting a unique violation error because I'm trying to create a key that already existed but was deactivated, how do I find that key? I think in my mind GetAttribute could maybe give everything in every state, but List APIs all take active state filters in, and the lookup GetByFQN is always strictly active for all policy it returns.

@@ -55,6 +55,21 @@ func (ns NullAttributeDefinitionRule) Value() (driver.Value, error) {
return string(ns.AttributeDefinitionRule), nil
}

type ActiveDefinitionPublicKeysView struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

With the database migrated, would you please run make policy-erd-gen to generate the updated ERD?

}

// Get freshly created key
ck, err := c.GetPublicKey(ctx, &kasregistry.GetKeyRequest{Id: id})
Copy link
Contributor

Choose a reason for hiding this comment

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

We've been trying to avoid doing immediate GETs during CREATEs to prevent data consistency issues in a system with multiple DB replicas (planning in advance). Would you mind employing either:

  • a transaction
    func (c *PolicyDBClient) RunInTx(ctx context.Context, query func(txClient *PolicyDBClient) error) error {
  • populating everything in the response from the RETURNING SQL and request?

Copy link
Member Author

Choose a reason for hiding this comment

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

wrapped it in a transaction

km.definition_id;

-- Trigger for attribute_value_key_map
CREATE TRIGGER trigger_update_was_used_value
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please help me understand the tradeoff you chose keeping this permanent state in sync with a column and transactions instead of doing the JOINs as needed when checking if a key is assigned to a policy object?

@@ -0,0 +1,233 @@
-- +goose Up
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please add a markdown file service/policy/db/migrations/20241125220354_keys_table.md that links to the ADR so we have that historical link between the migration and the relational change?

Copy link
Contributor

@jakedoublev jakedoublev left a comment

Choose a reason for hiding this comment

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

This is a lot of work. Thanks for tackling this @strantalis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants