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

Adds support for hidden private keys in server tenants #23379

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

dhr-verma
Copy link
Contributor

@dhr-verma dhr-verma commented Dec 19, 2024

Description

This PR adds support for storing private keys in the tenants DB collection. These private keys will be used to sign tokens that are acquired for keyless access. These are never shared with the client and will be rotated based on their rotation schedule. It also adds a new claim in the fluid access token - fluidRelayKeylessAccess. This claim will be used to determine how to validate the access token - with the shared keys or the private keys. This allows to keep access tokens to be backward compatible while also supporting keyless access. It does introduce some new APIs and modifies existing APIs:

  1. /tenants/:id/keys: Adds a new query parameter - getPrivateKeys. When set to true, it returns the privateKeys for the tenant. By default, this flag is set to false.
  2. /tenants/:id/keylessAccess: Toggles the enableKeylessAccess flag for a tenant.
  3. /tenants/:id/key: Refreshes the requested tenant key. When refreshPrivateKey is true it will refresh the private keys but does not return the refreshed keys. This is because key refreshes of the private keys will not be exposed to clients.
  4. /tenants/:id?: Creates a fluid tenant. This request accepts a new parameter in the body - enableKeylessAccess. When this is set to true, a tenant with support for private keys will be created. By default, this is false.

To support these, the following changes are done:

  1. Updated Riddler APIs to support a new interface ITenantPrivateKeys. The workflows essentially are the same as before but they include some new flags which helps riddler determine what keys to get/refresh/use for validation. The changes also include creating different caching keys for shared and private keys.
  2. Added a new claim fluidRelayKeylessAccess which will be set to true when generating a keyless access token.
  3. To test and ensure backward compatibility, I also created a test suite for tenantManager.ts. This ensures the existing APIs work as before. It also tests the new functionality. To further test these changes, I ran the server locally using Docker.

Breaking Changes

This adds a bunch of functionalities to support private hidden keys. However, these should not break existing changes.

Reviewer Guidance

  1. General design of reusing the existing design of getTenantKeys rather than creating new interfaces.
  2. Unit test clarity.

@Copilot Copilot bot review requested due to automatic review settings December 19, 2024 19:58
@github-actions github-actions bot added area: server Server related issues (routerlicious) base: main PRs targeted against main branch labels Dec 19, 2024
@@ -142,11 +161,15 @@ export function create(
const tenantCustomData: ITenantCustomData = request.body.customData
? request.body.customData
: {};
const enableKeylessAccess = request.body.enableKeylessAccess
? request.body.enableKeylessAccess
: null;
const tenantP = manager.createTenant(

Choose a reason for hiding this comment

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

This method should also have "enableKeyAcess". Both "enableKeylessAcess" and "enableKeyAcess" can be true, either of them can be true. If both of them are false, then it is an error condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not think we need another flag - enableKeyAccess. Access via keys will continue to work even if enableKeylessAccess is set to true.

const enableKeylessAccess = request.body.enableKeylessAccess
? request.body.enableKeylessAccess
: null;
const storageP = manager.updateKeylessAccessPolicy(tenantId, enableKeylessAccess);

Choose a reason for hiding this comment

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

We should be able to update both enableKeylessAccess and enableKeyAccess. Ensure that both cannot be set to false.

Copy link
Contributor Author

@dhr-verma dhr-verma Dec 19, 2024

Choose a reason for hiding this comment

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

Ahh okay got it. So in case both are true, then we will have (key, secondaryKey) and privateKeys present for the tenant. In case only enableKeyAccess is true, then we would only have (key, secondaryKey) present. In case only enableKeylessAccess is true, then we would only have privateKeys present for the tenant. Is my understanding correct?

@@ -128,7 +146,8 @@ export function create(
router.put("/tenants/:id/key", (request, response) => {
const tenantId = request.params.id;
const keyName = request.body.keyName as string;
const refreshKeyP = manager.refreshTenantKey(tenantId, keyName);
const refreshPrivateKey = request.body.refreshPrivateKey as boolean;
Copy link

@manishgargd manishgargd Dec 19, 2024

Choose a reason for hiding this comment

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

Ensure that the tenant cannot send us a request to "refreshPrivateKey".
"refreshPrivateKey" should be exposed only to internal APIs and not exposed to the tenant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the /tenants/:id/key API with refreshPrivateKey as true would only be called by internal APIs that will refresh the private tenant keys periodically.

@@ -57,6 +60,9 @@ export interface ITenantDocument {
// Timestamp of when this tenant will be hard deleted.
// Only applicable if the tenant is disabled.
scheduledDeletionTime?: string;

// Optional private keys, used for keyless tenants

Choose a reason for hiding this comment

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

Even (key, secondaryKey) can be optional. However, both (key, secondaryKey) and ITenantPrivateKeys cannot be optional. Only one can be at a given time.

@dhr-verma dhr-verma requested a review from znewton December 19, 2024 21:30
@@ -261,6 +278,7 @@ export class TenantManager {
storage: tenant.storage,
customData: tenant.customData,
scheduledDeletionTime: tenant.scheduledDeletionTime,
enableKeylessAccess: tenant.privateKeys ? true : false,

Choose a reason for hiding this comment

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

I think it should have another property "enableKeyAccess".

@@ -276,9 +294,55 @@ export class TenantManager {
storage: tenant.storage,
customData: tenant.customData,
scheduledDeletionTime: tenant.scheduledDeletionTime,
enableKeylessAccess: tenant.privateKeys ? true : false,

Choose a reason for hiding this comment

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

This should also have the property "enableKeyAccess"

@@ -287,6 +351,7 @@ export class TenantManager {
storage: ITenantStorage,
orderer: ITenantOrderer,
customData: ITenantCustomData,
enableKeylessAccess = false,
): Promise<ITenantConfig & { key: string }> {
const latestKeyVersion = this.secretManager.getLatestKeyVersion();

Choose a reason for hiding this comment

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

The function above could be called "createTenantKeys" and it could be called twice. Once for non-private keys and once for private keys. Lines 358 to 376 would not be required.

const latestKeyVersion = this.secretManager.getLatestKeyVersion();
let privateKeys: ITenantPrivateKeys | undefined;
// If the tenant is being converted to a keyless tenant, generate new private keys, otherwise update them to be undefined
if (enableKeylessAccess) {
Copy link

@manishgargd manishgargd Dec 20, 2024

Choose a reason for hiding this comment

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

If enableKeylessAccess is false, should we remove it? Check Azure Store or Redis if they allow to reset keylessAcceess, once it has been enabled.

If enableKeylessAccess is true, and it was already true in tenantMetadata, should we just ignore and return success for this operation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: server Server related issues (routerlicious) base: main PRs targeted against main branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants