Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added support for Customer-managed encryption keys (CMEK) on Firestore databases. (#7479)
25 changes: 20 additions & 5 deletions src/commands/firestore-databases-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export const command = new Command("firestore:databases:create <database>")
"--point-in-time-recovery <enablement>",
"Whether to enable the PITR feature on this database, for example 'ENABLED' or 'DISABLED'. Default is 'DISABLED'",
)
// TODO(b/356137854): Remove allowlist only message once feature is public GA.
.option(
"--kms-key-name <kmsKeyName>",
"The resource ID of a Cloud KMS key. If set, the database created will be a Customer-managed Encryption Key (CMEK) database encrypted with this key. This feature is allowlist only in initial launch.",
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a TODO with a buganizer link to clean this message up once its open to all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, have added.

)
.before(requirePermissions, ["datastore.databases.create"])
.before(warnEmulatorNotSupported, Emulators.FIRESTORE)
.action(async (database: string, options: FirestoreOptions) => {
Expand Down Expand Up @@ -63,14 +68,24 @@ export const command = new Command("firestore:databases:create <database>")
? types.PointInTimeRecoveryEnablement.ENABLED
: types.PointInTimeRecoveryEnablement.DISABLED;

const databaseResp: types.DatabaseResp = await api.createDatabase(
options.project,
database,
options.location,
let cmekConfig: types.CmekConfig | undefined;
if (options.kmsKeyName) {
cmekConfig = {
kmsKeyName: options.kmsKeyName,
};
}

const createDatabaseReq: types.CreateDatabaseReq = {
project: options.project,
databaseId: database,
locationId: options.location,
type,
deleteProtectionState,
pointInTimeRecoveryEnablement,
);
cmekConfig,
Copy link
Member

Choose a reason for hiding this comment

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

Optional: Create database has 7 args - consider switching this to a parameter object (ie https://go/tott/550)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have switched to a param object, thanks

};

const databaseResp: types.DatabaseResp = await api.createDatabase(createDatabaseReq);

if (options.json) {
logger.info(JSON.stringify(databaseResp, undefined, 2));
Expand Down
17 changes: 17 additions & 0 deletions src/firestore/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ export interface DatabaseReq {
type?: DatabaseType;
deleteProtectionState?: DatabaseDeleteProtectionState;
pointInTimeRecoveryEnablement?: PointInTimeRecoveryEnablement;
cmekConfig?: CmekConfig;
}

export interface CreateDatabaseReq {
project: string;
databaseId: string;
locationId: string;
type: DatabaseType;
deleteProtectionState: DatabaseDeleteProtectionState;
pointInTimeRecoveryEnablement: PointInTimeRecoveryEnablement;
cmekConfig?: CmekConfig;
}

export interface DatabaseResp {
Expand All @@ -143,6 +154,7 @@ export interface DatabaseResp {
etag: string;
versionRetentionPeriod: string;
earliestVersionTime: string;
cmekConfig?: CmekConfig;
}

export interface RestoreDatabaseReq {
Expand All @@ -154,3 +166,8 @@ export enum RecurrenceType {
DAILY = "DAILY",
WEEKLY = "WEEKLY",
}

export interface CmekConfig {
kmsKeyName: string;
activeKeyVersion?: string[];
}
29 changes: 9 additions & 20 deletions src/firestore/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,29 +619,18 @@ export class FirestoreApi {

/**
* Create a named Firestore Database
* @param project the Firebase project id.
* @param databaseId the name of the Firestore Database
* @param locationId the id of the region the database will be created in
* @param type FIRESTORE_NATIVE or DATASTORE_MODE
* @param deleteProtectionState DELETE_PROTECTION_ENABLED or DELETE_PROTECTION_DISABLED
* @param pointInTimeRecoveryEnablement POINT_IN_TIME_RECOVERY_ENABLED or POINT_IN_TIME_RECOVERY_DISABLED
* @param req the request to create a database
*/
async createDatabase(
project: string,
databaseId: string,
locationId: string,
type: types.DatabaseType,
deleteProtectionState: types.DatabaseDeleteProtectionState,
pointInTimeRecoveryEnablement: types.PointInTimeRecoveryEnablement,
): Promise<types.DatabaseResp> {
const url = `/projects/${project}/databases`;
async createDatabase(req: types.CreateDatabaseReq): Promise<types.DatabaseResp> {
const url = `/projects/${req.project}/databases`;
const payload: types.DatabaseReq = {
type,
locationId,
deleteProtectionState,
pointInTimeRecoveryEnablement,
locationId: req.locationId,
type: req.type,
deleteProtectionState: req.deleteProtectionState,
pointInTimeRecoveryEnablement: req.pointInTimeRecoveryEnablement,
cmekConfig: req.cmekConfig,
};
const options = { queryParams: { databaseId: databaseId } };
const options = { queryParams: { databaseId: req.databaseId } };
const res = await this.apiClient.post<types.DatabaseReq, { response?: types.DatabaseResp }>(
url,
payload,
Expand Down
1 change: 1 addition & 0 deletions src/firestore/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface FirestoreOptions extends Options {
type?: types.DatabaseType;
deleteProtection?: types.DatabaseDeleteProtectionStateOption;
pointInTimeRecoveryEnablement?: types.PointInTimeRecoveryEnablementOption;
kmsKeyName?: string;

// backup schedules
backupSchedule?: string;
Expand Down
12 changes: 12 additions & 0 deletions src/firestore/pretty-print.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,15 @@ describe("firebaseConsoleDatabaseUrl", () => {
);
});
});

describe("prettyStringArray", () => {
it("should correctly print an array of strings", () => {
expect(printer.prettyStringArray(["kms-key-1", "kms-key-2"])).to.equal(
"kms-key-1\nkms-key-2\n",
);
});

it("should print nothing if the array is empty", () => {
expect(printer.prettyStringArray([])).to.equal("");
});
});
31 changes: 30 additions & 1 deletion src/firestore/pretty-print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,14 @@ export class PrettyPrint {
* @param database the Firestore database.
*/
prettyPrintDatabase(database: types.DatabaseResp): void {
let colValueWidth = Math.max(50, 5 + database.name.length);
if (database.cmekConfig) {
colValueWidth = Math.max(140, 20 + database.cmekConfig.kmsKeyName.length);
}

const table = new Table({
head: ["Field", "Value"],
colWidths: [25, Math.max(50, 5 + database.name.length)],
colWidths: [30, colValueWidth],
});

table.push(
Expand All @@ -65,9 +70,33 @@ export class PrettyPrint {
["Earliest Version Time", clc.yellow(database.earliestVersionTime)],
["Version Retention Period", clc.yellow(database.versionRetentionPeriod)],
);

if (database.cmekConfig) {
table.push(["KMS Key Name", clc.yellow(database.cmekConfig.kmsKeyName)]);

if (database.cmekConfig.activeKeyVersion) {
table.push([
"Active Key Versions",
clc.yellow(this.prettyStringArray(database.cmekConfig.activeKeyVersion)),
]);
}
}

logger.info(table.toString());
}

/**
* Returns a pretty representation of a String array.
* @param stringArray the string array to be formatted.
*/
prettyStringArray(stringArray: string[]): string {
let result = "";
stringArray.forEach((str) => {
result += `${str}\n`;
});
return result;
}

/**
* Print an array of backups to the console as an ASCII table.
* @param backups the array of Firestore backups.
Expand Down