From 6bdc714e11304ae6281018308fc5c6042fa8caf8 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Mon, 8 Apr 2024 16:52:09 -0400 Subject: [PATCH 1/5] activate Mongo Proxy and Cassandra Proxy in Prod --- src/Common/MongoProxyClient.ts | 6 +++++- src/Explorer/Tables/TableDataClient.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 749248919..aa0c2321a 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -690,7 +690,11 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter } function useMongoProxyEndpoint(api: string): boolean { - const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac]; + const activeMongoProxyEndpoints: string[] = [ + MongoProxyEndpoints.Development, + MongoProxyEndpoints.Mpac, + MongoProxyEndpoints.Prod, + ]; let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if (userContext.databaseAccount.properties.ipRules?.length > 0) { canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 097ade395..e61c1b9c7 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -3,6 +3,7 @@ import * as ko from "knockout"; import Q from "q"; import { AuthType } from "../../AuthType"; import * as Constants from "../../Common/Constants"; +import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants"; import { handleError } from "../../Common/ErrorHandlingUtils"; import * as HeadersUtility from "../../Common/HeadersUtility"; import { createDocument } from "../../Common/dataAccess/createDocument"; @@ -19,7 +20,6 @@ import Explorer from "../Explorer"; import * as TableConstants from "./Constants"; import * as Entities from "./Entities"; import * as TableEntityProcessor from "./TableEntityProcessor"; -import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants"; export interface CassandraTableKeys { partitionKeys: CassandraTableKey[]; @@ -732,7 +732,11 @@ export class CassandraAPIDataClient extends TableDataClient { } private useCassandraProxyEndpoint(api: string): boolean { - const activeCassandraProxyEndpoints: string[] = [CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac]; + const activeCassandraProxyEndpoints: string[] = [ + CassandraProxyEndpoints.Development, + CassandraProxyEndpoints.Mpac, + CassandraProxyEndpoints.Prod, + ]; let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if (userContext.databaseAccount.properties.ipRules?.length > 0) { canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED; From 16c7b2567b2251f42b691c18547f54891c63229a Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Tue, 9 Apr 2024 11:39:11 -0400 Subject: [PATCH 2/5] fix bug that blocked local mongo proxy and cassandra proxy development --- src/Common/MongoProxyClient.ts | 5 ++++- src/ConfigContext.ts | 1 + src/Explorer/Tables/TableDataClient.ts | 5 ++++- src/Explorer/Tabs/Tabs.tsx | 7 ++++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index aa0c2321a..b742a7f79 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -696,7 +696,10 @@ function useMongoProxyEndpoint(api: string): boolean { MongoProxyEndpoints.Prod, ]; let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; - if (userContext.databaseAccount.properties.ipRules?.length > 0) { + if ( + configContext.MONGO_PROXY_ENDPOINT != MongoProxyEndpoints.Development && + userContext.databaseAccount.properties.ipRules?.length > 0 + ) { canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; } diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 435166b90..a8931ee64 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -243,3 +243,4 @@ export async function initializeConfiguration(): Promise { } export { configContext }; + diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index e61c1b9c7..91cd233f5 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -738,7 +738,10 @@ export class CassandraAPIDataClient extends TableDataClient { CassandraProxyEndpoints.Prod, ]; let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; - if (userContext.databaseAccount.properties.ipRules?.length > 0) { + if ( + configContext.CASSANDRA_PROXY_ENDPOINT != CassandraProxyEndpoints.Development && + userContext.databaseAccount.properties.ipRules?.length > 0 + ) { canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED; } diff --git a/src/Explorer/Tabs/Tabs.tsx b/src/Explorer/Tabs/Tabs.tsx index 4af95118b..53678ed5a 100644 --- a/src/Explorer/Tabs/Tabs.tsx +++ b/src/Explorer/Tabs/Tabs.tsx @@ -324,7 +324,12 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules; - if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) { + if ( + ((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) || + (userContext.apiType === "Cassandra" && + configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) && + ipRules?.length + ) { const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT]; const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange); const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) => From a712193477e37f982363d35b15404aa11755761d Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Tue, 9 Apr 2024 11:43:24 -0400 Subject: [PATCH 3/5] fix pr check tests --- src/Common/MongoProxyClient.ts | 2 +- src/ConfigContext.ts | 1 - src/Explorer/Tables/TableDataClient.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index b742a7f79..44400d874 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -697,7 +697,7 @@ function useMongoProxyEndpoint(api: string): boolean { ]; let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if ( - configContext.MONGO_PROXY_ENDPOINT != MongoProxyEndpoints.Development && + configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development && userContext.databaseAccount.properties.ipRules?.length > 0 ) { canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index a8931ee64..435166b90 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -243,4 +243,3 @@ export async function initializeConfiguration(): Promise { } export { configContext }; - diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 91cd233f5..c6882bc33 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -739,7 +739,7 @@ export class CassandraAPIDataClient extends TableDataClient { ]; let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if ( - configContext.CASSANDRA_PROXY_ENDPOINT != CassandraProxyEndpoints.Development && + configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development && userContext.databaseAccount.properties.ipRules?.length > 0 ) { canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED; From ef7c2fe2f762f98a3786445ebd9f42a0f958a3f0 Mon Sep 17 00:00:00 2001 From: Senthamil Sindhu Date: Wed, 10 Apr 2024 11:59:57 -0700 Subject: [PATCH 4/5] Remove dev endpoint --- src/Explorer/Tables/TableDataClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index c6882bc33..32e686520 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -733,7 +733,7 @@ export class CassandraAPIDataClient extends TableDataClient { private useCassandraProxyEndpoint(api: string): boolean { const activeCassandraProxyEndpoints: string[] = [ - CassandraProxyEndpoints.Development, + // CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac, CassandraProxyEndpoints.Prod, ]; From 10a8505b9a82857661263c59fa1dc42f1d7b0898 Mon Sep 17 00:00:00 2001 From: Senthamil Sindhu Date: Fri, 14 Jun 2024 12:12:30 -0700 Subject: [PATCH 5/5] Support data plane RBAC --- src/Common/Constants.ts | 3 + .../Panes/SettingsPane/SettingsPane.tsx | 45 ++++++++++++ src/Explorer/Tables/TableDataClient.ts | 1 - src/Platform/Hosted/extractFeatures.ts | 2 + src/Shared/StorageUtility.ts | 3 + src/UserContext.ts | 1 + src/hooks/useKnockoutExplorer.ts | 70 +++++++++++++++++-- 7 files changed, 118 insertions(+), 7 deletions(-) diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index 1eed03a4f..a4f3b3f9b 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -179,6 +179,9 @@ export class CassandraProxyAPIs { export class Queries { public static CustomPageOption: string = "custom"; public static UnlimitedPageOption: string = "unlimited"; + public static setAutomaticRBACOption: string = "Automatic"; + public static setTrueRBACOption: string = "True"; + public static setFalseRBACOption: string = "False"; public static itemsPerPage: number = 100; public static unlimitedItemsPerPage: number = 100; // TODO: Figure out appropriate value so it works for accounts with a large number of partitions public static containersPerPage: number = 50; diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 1f0eda4f7..0ac42a9bb 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -41,6 +41,13 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ ? Constants.Queries.UnlimitedPageOption : Constants.Queries.CustomPageOption, ); + const [enableDataPlaneRBACOption, setEnableDataPlaneRBACOption] = useState( + LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setAutomaticRBACOption + ? Constants.Queries.setAutomaticRBACOption + : LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) === Constants.Queries.setTrueRBACOption + ? Constants.Queries.setTrueRBACOption + : Constants.Queries.setFalseRBACOption + ); const [ruThresholdEnabled, setRUThresholdEnabled] = useState(isRUThresholdEnabled()); const [ruThreshold, setRUThreshold] = useState(getRUThreshold()); const [queryTimeoutEnabled, setQueryTimeoutEnabled] = useState( @@ -110,7 +117,14 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ StorageKey.ActualItemPerPage, isCustomPageOptionSelected() ? customItemPerPage : Constants.Queries.unlimitedItemsPerPage, ); + LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); + + LocalStorageUtility.setEntryString( + StorageKey.DataPlaneRbacEnabled, + enableDataPlaneRBACOption + ); + LocalStorageUtility.setEntryBoolean(StorageKey.RUThresholdEnabled, ruThresholdEnabled); LocalStorageUtility.setEntryBoolean(StorageKey.QueryTimeoutEnabled, queryTimeoutEnabled); LocalStorageUtility.setEntryNumber(StorageKey.RetryAttempts, retryAttempts); @@ -197,6 +211,12 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ { key: Constants.PriorityLevel.High, text: "High" }, ]; + const dataPlaneRBACOptionsList: IChoiceGroupOption[] = [ + { key: Constants.Queries.setAutomaticRBACOption, text: "Automatic" }, + { key: Constants.Queries.setTrueRBACOption, text: "True" }, + { key: Constants.Queries.setFalseRBACOption, text: "False"} + ]; + const handleOnPriorityLevelOptionChange = ( ev: React.FormEvent, option: IChoiceGroupOption, @@ -208,6 +228,10 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ setPageOption(option.key); }; + const handleOnDataPlaneRBACOptionChange = (ev: React.FormEvent, option: IChoiceGroupOption): void => { + setEnableDataPlaneRBACOption(option.key); + }; + const handleOnRUThresholdToggleChange = (ev: React.MouseEvent, checked?: boolean): void => { setRUThresholdEnabled(checked); }; @@ -361,6 +385,27 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ )} + {( +
+
+
+ + Enable DataPlane RBAC + + + Choose Automatic to enable DataPlane RBAC automatically. True/False to voluntarily enable/disable DataPlane RBAC + + +
+
+
+ )} {userContext.apiType === "SQL" && ( <>
diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 32e686520..740b4cf63 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -733,7 +733,6 @@ export class CassandraAPIDataClient extends TableDataClient { private useCassandraProxyEndpoint(api: string): boolean { const activeCassandraProxyEndpoints: string[] = [ - // CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac, CassandraProxyEndpoints.Prod, ]; diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 7bf3c8a3f..790b5d643 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -14,6 +14,7 @@ export type Features = { readonly enableTtl: boolean; readonly executeSproc: boolean; readonly enableAadDataPlane: boolean; + readonly enableDataPlaneRbac: boolean; readonly enableResourceGraph: boolean; readonly enableKoResourceTree: boolean; readonly hostedDataExplorer: boolean; @@ -74,6 +75,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear canExceedMaximumValue: "true" === get("canexceedmaximumvalue"), cosmosdb: "true" === get("cosmosdb"), enableAadDataPlane: "true" === get("enableaaddataplane"), + enableDataPlaneRbac: "true" === get("enabledataplanerbac"), enableResourceGraph: "true" === get("enableresourcegraph"), enableChangeFeedPolicy: "true" === get("enablechangefeedpolicy"), enableFixedCollectionWithSharedThroughput: "true" === get("enablefixedcollectionwithsharedthroughput"), diff --git a/src/Shared/StorageUtility.ts b/src/Shared/StorageUtility.ts index b229ac7db..3f43828fa 100644 --- a/src/Shared/StorageUtility.ts +++ b/src/Shared/StorageUtility.ts @@ -5,6 +5,9 @@ import * as StringUtility from "./StringUtility"; export { LocalStorageUtility, SessionStorageUtility }; export enum StorageKey { ActualItemPerPage, + DataPlaneRbacEnabled, + DataPlaneRbacDisabled, + isDataPlaneRbacAutomatic, RUThresholdEnabled, RUThreshold, QueryTimeoutEnabled, diff --git a/src/UserContext.ts b/src/UserContext.ts index 2fa1bb946..753f36583 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -101,6 +101,7 @@ interface UserContext { sampleDataConnectionInfo?: ParsedResourceTokenConnectionString; readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams; readonly feedbackPolicies?: AdminFeedbackPolicySettings; + readonly dataPlaneRbacEnabled?: boolean; } export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 4e80b3476..c46b944ad 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -4,6 +4,7 @@ import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesCon import Explorer from "Explorer/Explorer"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; +import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { useQueryCopilot } from "hooks/useQueryCopilot"; @@ -270,8 +271,30 @@ async function configureHostedWithAAD(config: AAD): Promise { } } try { - if (!account.properties.disableLocalAuth) { - keys = await listKeys(subscriptionId, resourceGroup, account.name); + if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) { + var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled); + if (isDataPlaneRbacSetting == "Automatic") + { + if (!account.properties.disableLocalAuth) { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + } + else { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + } + else if(isDataPlaneRbacSetting == "True") { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + else { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + updateUserContext({ + dataPlaneRbacEnabled: false + }); + } } } catch (e) { if (userContext.features.enableAadDataPlane) { @@ -393,8 +416,9 @@ async function configurePortal(): Promise { updateUserContext({ authType: AuthType.AAD, }); + let explorer: Explorer; - return new Promise((resolve) => { + return new Promise(async (resolve) => { // In development mode, try to load the iframe message from session storage. // This allows webpack hot reload to function properly in the portal if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) { @@ -407,6 +431,7 @@ async function configurePortal(): Promise { console.dir(message); updateContextsFromPortalMessage(message); explorer = new Explorer(); + // In development mode, save the iframe message from the portal in session storage. // This allows webpack hot reload to funciton properly if (process.env.NODE_ENV === "development") { @@ -415,11 +440,11 @@ async function configurePortal(): Promise { resolve(explorer); } } - + // In the Portal, configuration of Explorer happens via iframe message window.addEventListener( "message", - (event) => { + async (event) => { if (isInvalidParentFrameOrigin(event)) { return; } @@ -449,6 +474,37 @@ async function configurePortal(): Promise { setTimeout(() => explorer.openNPSSurveyDialog(), 3000); } + let dbAccount = userContext.databaseAccount; + let keys: DatabaseAccountListKeysResult = {}; + const account = userContext.databaseAccount; + const subscriptionId = userContext.subscriptionId; + const resourceGroup = userContext.resourceGroup; + if(LocalStorageUtility.hasItem(StorageKey.DataPlaneRbacEnabled)) { + var isDataPlaneRbacSetting = LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled); + if (isDataPlaneRbacSetting == "Automatic") + { + if (!account.properties.disableLocalAuth) { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + } + else { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + } + else if(isDataPlaneRbacSetting == "True") { + updateUserContext({ + dataPlaneRbacEnabled: true + }); + } + else { + keys = await listKeys(subscriptionId, resourceGroup, account.name); + updateUserContext({ + dataPlaneRbacEnabled: false + }); + } + } + if (openAction) { handleOpenAction(openAction, useDatabases.getState().databases, explorer); } @@ -469,9 +525,11 @@ async function configurePortal(): Promise { }, false, ); - + sendReadyMessage(); + }); + } function shouldForwardMessage(message: PortalMessage, messageOrigin: string) {