diff --git a/.github/workflows/cypress-test.yml b/.github/workflows/cypress-test.yml
new file mode 100644
index 000000000..0ba215226
--- /dev/null
+++ b/.github/workflows/cypress-test.yml
@@ -0,0 +1,131 @@
+name: Cypress Tests
+
+on: [push, pull_request]
+
+env:
+  TEST_BROWSER_HEADLESS: 1
+  CI: 1
+  FTR_PATH: 'ftr'
+  START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --opensearch_security.multitenancy.enable_aggregation_view=true'
+  OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot'
+  SPEC: 'cypress/integration/plugins/security-dashboards-plugin/aggregation_view.js,'
+
+jobs:
+  tests:
+    name: Run aggregation view cypress test
+    runs-on: ubuntu-latest
+    steps:
+      - name: Download OpenSearch Core
+        run: |
+          wget https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/3.0.0/latest/linux/x64/tar/builds/opensearch/dist/opensearch-min-3.0.0-linux-x64.tar.gz
+          tar -xzf opensearch-*.tar.gz
+          rm -f opensearch-*.tar.gz
+          
+      - name: Download OpenSearch Security Plugin
+        run: wget -O opensearch-security.zip https://ci.opensearch.org/ci/dbc/distribution-build-opensearch/3.0.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-3.0.0.0.zip
+        
+
+      - name: Run OpenSearch with plugin
+        run: |
+          cat > os-ep.sh <<EOF
+          yes | opensearch-plugin install file:///docker-host/security-plugin.zip
+          chmod +x plugins/opensearch-security/tools/install_demo_configuration.sh
+          yes | plugins/opensearch-security/tools/install_demo_configuration.sh
+          echo "plugins.security.unsupported.restapi.allow_securityconfig_modification: true" >> /opensearch/config/opensearch.yml
+          chown 1001:1001 -R /opensearch
+          su -c "/opensearch/bin/opensearch" -s /bin/bash opensearch
+          EOF
+          docker build -t opensearch-test:latest -f- . <<EOF
+          FROM ubuntu:latest
+          COPY --chown=1001:1001 os-ep.sh /docker-host/
+          COPY --chown=1001:1001 opensearch-security.zip /docker-host/security-plugin.zip
+          COPY --chown=1001:1001 opensearch* /opensearch/
+          RUN chmod +x /docker-host/os-ep.sh
+          RUN useradd -u 1001 -s /sbin/nologin opensearch
+          ENV PATH="/opensearch/bin:${PATH}"
+          WORKDIR /opensearch/
+          ENTRYPOINT /docker-host/os-ep.sh
+          EOF
+          docker run -d -p 9200:9200 -p 9600:9600 -i opensearch-test:latest
+
+      - name: Checkout OpenSearch Dashboard
+        uses: actions/checkout@v2
+        with:
+          path: OpenSearch-Dashboards
+          repository: opensearch-project/OpenSearch-Dashboards
+          ref: 'main'
+          fetch-depth: 0
+      
+      - name: Create plugins dir
+        run: |
+          cd ./OpenSearch-Dashboards
+          mkdir -p plugins
+      
+      - name: Checkout OpenSearch Dashboard Security plugin
+        uses: actions/checkout@v2
+        with:
+          path: OpenSearch-Dashboards/plugins/security-dashboards-plugin
+          ref: ${{ github.ref }}
+
+      - name: Check OpenSearch Running
+        continue-on-error: true
+        run: curl -XGET https://localhost:9200 -u 'admin:admin' -k
+
+      - name: Get node and yarn versions
+        id: versions
+        run: |
+          echo "::set-output name=node_version::$(cat ./OpenSearch-Dashboards/.node-version)"
+          echo "::set-output name=yarn_version::$(jq -r '.engines.yarn' ./OpenSearch-Dashboards/package.json)"
+      
+      - name: Setup node
+        uses: actions/setup-node@v1
+        with:
+          node-version: ${{ steps.versions.outputs.node_version }}
+          registry-url: 'https://registry.npmjs.org'
+      
+      - name: Install correct yarn version for OpenSearch Dashboards
+        run: |
+          npm uninstall -g yarn
+          echo "Installing yarn ${{ steps.versions_step.outputs.yarn_version }}"
+          npm i -g yarn@${{ steps.versions.outputs.yarn_version }}
+      
+      - name: Check OpenSearch Running
+        continue-on-error: true
+        run: curl -XGET https://localhost:9200 -u 'admin:admin' -k
+
+      - name: Bootstrap OpenSearch Dashboards
+        continue-on-error: false
+        run: |
+          cd ./OpenSearch-Dashboards
+          yarn osd bootstrap
+          echo 'server.host: "0.0.0.0"' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch.hosts: ["https://localhost:9200"]' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch.ssl.verificationMode: none' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch.username: "kibanaserver"' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch.password: "kibanaserver"' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch.requestHeadersWhitelist: [ authorization,securitytenant ]' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch_security.multitenancy.enabled: true' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"]' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch_security.readonly_mode.roles: ["kibana_read_only"]' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch_security.cookie.secure: false' >> ./config/opensearch_dashboards.yml
+          echo 'opensearch_security.multitenancy.enable_aggregation_view: true' >> ./config/opensearch_dashboards.yml
+          yarn start --no-base-path --no-watch &
+          sleep 300
+
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          path: ${{ env.FTR_PATH }}
+          repository: opensearch-project/opensearch-dashboards-functional-test
+          ref: 'main'
+
+      - name: Get Cypress version
+        id: cypress_version
+        run: |
+          echo "::set-output name=cypress_version::$(cat ./${{ env.FTR_PATH }}/package.json | jq '.devDependencies.cypress' | tr -d '"')"
+      
+      - name: Run tests
+        uses: cypress-io/github-action@v2
+        with:
+          working-directory: ${{ env.FTR_PATH }}
+          command: yarn cypress:run-with-security-and-aggregation-view --browser chromium --spec ${{ env.SPEC }}
diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json
index b9a2096c9..e75f76792 100644
--- a/opensearch_dashboards.json
+++ b/opensearch_dashboards.json
@@ -3,7 +3,7 @@
   "version": "3.0.0.0",
   "opensearchDashboardsVersion": "3.0.0",
   "configPath": ["opensearch_security"],
-  "requiredPlugins": ["navigation"],
+  "requiredPlugins": ["navigation", "savedObjectsManagement"],
   "server": true,
   "ui": true
 }
\ No newline at end of file
diff --git a/public/apps/account/account-app.tsx b/public/apps/account/account-app.tsx
index 0ceb8f5e3..590d827ea 100644
--- a/public/apps/account/account-app.tsx
+++ b/public/apps/account/account-app.tsx
@@ -20,7 +20,7 @@ import { AccountNavButton } from './account-nav-button';
 import { fetchAccountInfoSafe } from './utils';
 import { ClientConfigType } from '../../types';
 import { CUSTOM_ERROR_PAGE_URI, ERROR_MISSING_ROLE_PATH } from '../../../common';
-import { selectTenant } from '../configuration/utils/tenant-utils';
+import { fetchCurrentTenant, selectTenant } from '../configuration/utils/tenant-utils';
 import {
   getSavedTenant,
   getShouldShowTenantPopup,
@@ -44,7 +44,16 @@ export async function setupTopNavButton(coreStart: CoreStart, config: ClientConf
         coreStart.http.basePath.serverBasePath + CUSTOM_ERROR_PAGE_URI + ERROR_MISSING_ROLE_PATH;
     }
 
-    let tenant = accountInfo.user_requested_tenant;
+    let tenant: string | undefined;
+    if (config.multitenancy.enabled) {
+      try {
+        tenant = await fetchCurrentTenant(coreStart.http);
+      } catch (e) {
+        tenant = undefined;
+        console.log(e);
+      }
+    }
+
     let shouldShowTenantPopup = true;
 
     if (tenantSpecifiedInUrl() || getShouldShowTenantPopup() === false) {
@@ -67,7 +76,7 @@ export async function setupTopNavButton(coreStart: CoreStart, config: ClientConf
             window.location.reload();
           }
         }
-      } catch (e) {
+      } catch (e: any) {
         constructErrorMessageAndLog(e, `Failed to switch to ${tenant} tenant.`);
       }
     }
diff --git a/public/apps/account/account-nav-button.tsx b/public/apps/account/account-nav-button.tsx
index 7bd0e578b..1ca3360b1 100644
--- a/public/apps/account/account-nav-button.tsx
+++ b/public/apps/account/account-nav-button.tsx
@@ -61,9 +61,10 @@ export function AccountNavButton(props: {
             setModal(null);
             window.location.reload();
           }}
+          tenant={props.tenant!}
         />
       ),
-    [props.config, props.coreStart]
+    [props.config, props.coreStart, props.tenant]
   );
 
   // Check if the tenant modal should be shown on load
diff --git a/public/apps/account/tenant-switch-panel.tsx b/public/apps/account/tenant-switch-panel.tsx
index 079332015..96d5b9659 100755
--- a/public/apps/account/tenant-switch-panel.tsx
+++ b/public/apps/account/tenant-switch-panel.tsx
@@ -48,6 +48,7 @@ interface TenantSwitchPanelProps {
   handleClose: () => void;
   handleSwitchAndClose: () => void;
   config: ClientConfigType;
+  tenant: string;
 }
 
 const GLOBAL_TENANT_KEY_NAME = 'global_tenant';
@@ -90,8 +91,12 @@ export function TenantSwitchPanel(props: TenantSwitchPanelProps) {
         const currentUserName = accountInfo.data.user_name;
         setUsername(currentUserName);
 
-        // @ts-ignore
-        const currentRawTenantName = accountInfo.data.user_requested_tenant;
+        let currentRawTenantName: string | undefined;
+        if (props.config.multitenancy.enable_aggregation_view) {
+          currentRawTenantName = props.tenant;
+        } else {
+          currentRawTenantName = accountInfo.data.user_requested_tenant;
+        }
         setCurrentTenant(currentRawTenantName || '', currentUserName);
       } catch (e) {
         // TODO: switch to better error display.
@@ -100,7 +105,7 @@ export function TenantSwitchPanel(props: TenantSwitchPanelProps) {
     };
 
     fetchData();
-  }, [props.coreStart.http]);
+  }, [props.coreStart.http, props.tenant, props.config.multitenancy]);
 
   // Custom tenant super select related.
   const onCustomTenantChange = (selectedOption: EuiComboBoxOptionOption[]) => {
diff --git a/public/apps/account/test/account-app.test.tsx b/public/apps/account/test/account-app.test.tsx
index fa5d45ba0..52282ab72 100644
--- a/public/apps/account/test/account-app.test.tsx
+++ b/public/apps/account/test/account-app.test.tsx
@@ -22,7 +22,7 @@ import {
   getSavedTenant,
 } from '../../../utils/storage-utils';
 import { fetchAccountInfoSafe } from '../utils';
-import { selectTenant } from '../../configuration/utils/tenant-utils';
+import { fetchCurrentTenant, selectTenant } from '../../configuration/utils/tenant-utils';
 
 jest.mock('../../../utils/storage-utils', () => ({
   getShouldShowTenantPopup: jest.fn(),
@@ -36,6 +36,7 @@ jest.mock('../utils', () => ({
 
 jest.mock('../../configuration/utils/tenant-utils', () => ({
   selectTenant: jest.fn(),
+  fetchCurrentTenant: jest.fn(),
 }));
 
 describe('Account app', () => {
@@ -47,6 +48,12 @@ describe('Account app', () => {
     },
   };
 
+  const mockConfig = {
+    multitenancy: {
+      enable_aggregation_view: true,
+    },
+  };
+
   const mockAccountInfo = {
     data: {
       roles: {
@@ -55,8 +62,11 @@ describe('Account app', () => {
     },
   };
 
+  const mockTenant = 'test1';
+
   beforeAll(() => {
     (fetchAccountInfoSafe as jest.Mock).mockResolvedValue(mockAccountInfo);
+    (fetchCurrentTenant as jest.Mock).mockResolvedValue(mockTenant);
   });
 
   it('Should skip if auto swich if securitytenant in url', (done) => {
@@ -65,7 +75,7 @@ describe('Account app', () => {
     delete window.location;
     window.location = new URL('http://www.example.com?securitytenant=abc') as any;
 
-    setupTopNavButton(mockCoreStart, {} as any);
+    setupTopNavButton(mockCoreStart, mockConfig as any);
 
     process.nextTick(() => {
       expect(setShouldShowTenantPopup).toBeCalledWith(false);
@@ -77,7 +87,7 @@ describe('Account app', () => {
   it('Should switch to saved tenant when securitytenant not in url', (done) => {
     (getSavedTenant as jest.Mock).mockReturnValueOnce('tenant1');
 
-    setupTopNavButton(mockCoreStart, {} as any);
+    setupTopNavButton(mockCoreStart, mockConfig as any);
 
     process.nextTick(() => {
       expect(getSavedTenant).toBeCalledTimes(1);
@@ -92,7 +102,7 @@ describe('Account app', () => {
   it('Should show tenant selection popup when neither securitytenant in url nor saved tenant', (done) => {
     (getSavedTenant as jest.Mock).mockReturnValueOnce(null);
 
-    setupTopNavButton(mockCoreStart, {} as any);
+    setupTopNavButton(mockCoreStart, mockConfig as any);
 
     process.nextTick(() => {
       expect(getSavedTenant).toBeCalledTimes(1);
diff --git a/public/apps/configuration/configuration-app.tsx b/public/apps/configuration/configuration-app.tsx
index 83ae62846..a2294315d 100644
--- a/public/apps/configuration/configuration-app.tsx
+++ b/public/apps/configuration/configuration-app.tsx
@@ -19,12 +19,12 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import { I18nProvider } from '@osd/i18n/react';
 import { AppMountParameters, CoreStart } from '../../../../../src/core/public';
-import { AppPluginStartDependencies, ClientConfigType } from '../../types';
+import { SecurityPluginStartDependencies, ClientConfigType } from '../../types';
 import { AppRouter } from './app-router';
 
 export function renderApp(
   coreStart: CoreStart,
-  navigation: AppPluginStartDependencies,
+  navigation: SecurityPluginStartDependencies,
   params: AppMountParameters,
   config: ClientConfigType
 ) {
diff --git a/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx b/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx
index b6e1d4871..e80d0d0e1 100644
--- a/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx
+++ b/public/apps/configuration/panels/role-edit/cluster-permission-panel.tsx
@@ -49,6 +49,7 @@ export function ClusterPermissionPanel(props: {
                 options={optionUniverse}
                 selectedOptions={state}
                 onChange={setState}
+                id="roles-cluster-permission-box"
               />
             </EuiFlexItem>
             {/* TODO: 'Browse and select' button with a pop-up modal for selection */}
diff --git a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx
index 4c0e0ae7b..75e2856e4 100644
--- a/public/apps/configuration/panels/role-edit/index-permission-panel.tsx
+++ b/public/apps/configuration/panels/role-edit/index-permission-panel.tsx
@@ -119,13 +119,16 @@ export function IndexPatternRow(props: {
 }) {
   return (
     <FormRow headerText="Index" helpText="Specify index pattern using *">
-      <EuiComboBox
-        noSuggestions
-        placeholder="Search for index name or type in index pattern"
-        selectedOptions={props.value}
-        onChange={props.onChangeHandler}
-        onCreateOption={props.onCreateHandler}
-      />
+      <EuiFlexItem className={LIMIT_WIDTH_INPUT_CLASS}>
+        <EuiComboBox
+          noSuggestions
+          placeholder="Search for index name or type in index pattern"
+          selectedOptions={props.value}
+          onChange={props.onChangeHandler}
+          onCreateOption={props.onCreateHandler}
+          id="index-input-box"
+        />
+      </EuiFlexItem>
     </FormRow>
   );
 }
@@ -150,6 +153,7 @@ export function IndexPermissionRow(props: {
             options={props.permisionOptionsSet}
             selectedOptions={props.value}
             onChange={props.onChangeHandler}
+            id="roles-index-permission-box"
           />
         </EuiFlexItem>
         {/* TODO: 'Browse and select' button with a pop-up modal for selection */}
diff --git a/public/apps/configuration/panels/role-edit/tenant-panel.tsx b/public/apps/configuration/panels/role-edit/tenant-panel.tsx
index a2c239755..2cce9e546 100644
--- a/public/apps/configuration/panels/role-edit/tenant-panel.tsx
+++ b/public/apps/configuration/panels/role-edit/tenant-panel.tsx
@@ -91,6 +91,7 @@ function generateTenantPermissionPanels(
               onChange={onValueChangeHandler('tenantPatterns')}
               onCreateOption={onCreateOptionHandler('tenantPatterns')}
               options={permisionOptionsSet}
+              id="roles-tenant-permission-box"
             />
           </EuiFlexItem>
           <EuiFlexItem style={{ maxWidth: '170px' }}>
diff --git a/public/apps/configuration/utils/tenant-utils.tsx b/public/apps/configuration/utils/tenant-utils.tsx
index 080a93f1c..8c50ffcda 100644
--- a/public/apps/configuration/utils/tenant-utils.tsx
+++ b/public/apps/configuration/utils/tenant-utils.tsx
@@ -15,6 +15,8 @@
 
 import { HttpStart } from 'opensearch-dashboards/public';
 import { map } from 'lodash';
+import React from 'react';
+import { i18n } from '@osd/i18n';
 import {
   API_ENDPOINT_TENANTS,
   API_ENDPOINT_MULTITENANCY,
@@ -36,9 +38,15 @@ import { httpDelete, httpGet, httpPost } from './request-utils';
 import { getResourceUrl } from './resource-utils';
 
 export const globalTenantName = 'global_tenant';
+export const GLOBAL_TENANT_SYMBOL = '';
+export const PRIVATE_TENANT_SYMBOL = '__user__';
+export const DEFAULT_TENANT = 'default';
+export const GLOBAL_TENANT_RENDERING_TEXT = 'Global';
+export const PRIVATE_TENANT_RENDERING_TEXT = 'Private';
+
 export const GLOBAL_USER_DICT: { [key: string]: string } = {
   Label: 'Global',
-  Value: '',
+  Value: GLOBAL_TENANT_SYMBOL,
   Description: 'Everyone can see it',
 };
 
@@ -62,10 +70,10 @@ export function transformTenantData(
 ): Tenant[] {
   // @ts-ignore
   const tenantList: Tenant[] = map<Tenant, Tenant>(rawTenantData, (v: Tenant, k?: string) => ({
-    tenant: k === globalTenantName ? GLOBAL_USER_DICT.Label : k || '',
+    tenant: k === globalTenantName ? GLOBAL_USER_DICT.Label : k || GLOBAL_TENANT_SYMBOL,
     reserved: v.reserved,
     description: k === globalTenantName ? GLOBAL_USER_DICT.Description : v.description,
-    tenantValue: k === globalTenantName ? GLOBAL_USER_DICT.Value : k || '',
+    tenantValue: k === globalTenantName ? GLOBAL_USER_DICT.Value : k || GLOBAL_TENANT_SYMBOL,
   }));
   if (isPrivateEnabled) {
     // Insert Private Tenant in List
@@ -170,3 +178,37 @@ export function transformRoleTenantPermissions(
     permissionType: getTenantPermissionType(tenantPermission.allowed_actions),
   }));
 }
+
+export function isPrivateTenant(selectedTenant: string | null) {
+  return selectedTenant !== null && selectedTenant === PRIVATE_TENANT_SYMBOL;
+}
+
+export function isRenderingPrivateTenant(selectedTenant: string | null) {
+  return selectedTenant !== null && selectedTenant?.startsWith(PRIVATE_TENANT_SYMBOL);
+}
+
+export function isGlobalTenant(selectedTenant: string | null) {
+  return selectedTenant !== null && selectedTenant === GLOBAL_TENANT_SYMBOL;
+}
+
+export const tenantColumn = {
+  id: 'tenant_column',
+  euiColumn: {
+    field: 'namespaces',
+    name: <div>Tenant</div>,
+    dataType: 'string',
+    render: (value: any[][]) => {
+      let text = value.flat()[0];
+      if (isGlobalTenant(text)) {
+        text = GLOBAL_TENANT_RENDERING_TEXT;
+      } else if (isRenderingPrivateTenant(text)) {
+        text = PRIVATE_TENANT_RENDERING_TEXT;
+      }
+      text = i18n.translate('savedObjectsManagement.objectsTable.table.columnTenantName', {
+        defaultMessage: text,
+      });
+      return <div>{text}</div>;
+    },
+  },
+  loadData: () => {},
+};
diff --git a/public/apps/types.ts b/public/apps/types.ts
index 0ca39e1b0..3f5c870b0 100644
--- a/public/apps/types.ts
+++ b/public/apps/types.ts
@@ -14,11 +14,11 @@
  */
 
 import { AppMountParameters, CoreStart } from '../../../../src/core/public';
-import { AppPluginStartDependencies, ClientConfigType } from '../types';
+import { SecurityPluginStartDependencies, ClientConfigType } from '../types';
 
 export interface AppDependencies {
   coreStart: CoreStart;
-  navigation: AppPluginStartDependencies;
+  navigation: SecurityPluginStartDependencies;
   params: AppMountParameters;
   config: ClientConfigType;
 }
diff --git a/public/plugin.ts b/public/plugin.ts
index 1ecf452fb..2adb33a83 100644
--- a/public/plugin.ts
+++ b/public/plugin.ts
@@ -14,6 +14,7 @@
  */
 
 import { BehaviorSubject } from 'rxjs';
+import { SavedObjectsManagementColumn } from 'src/plugins/saved_objects_management/public';
 import {
   AppMountParameters,
   AppStatus,
@@ -37,13 +38,15 @@ import {
   excludeFromDisabledTransportCategories,
 } from './apps/configuration/panels/audit-logging/constants';
 import {
-  AppPluginStartDependencies,
+  SecurityPluginStartDependencies,
   ClientConfigType,
   SecurityPluginSetup,
   SecurityPluginStart,
+  SecurityPluginSetupDependencies,
 } from './types';
 import { addTenantToShareURL } from './services/shared-link';
 import { interceptError } from './utils/logout-utils';
+import { tenantColumn } from './apps/configuration/utils/tenant-utils';
 
 async function hasApiPermission(core: CoreSetup): Promise<boolean | undefined> {
   try {
@@ -62,12 +65,24 @@ const APP_ID_DASHBOARDS = 'dashboards';
 // OpenSearchDashboards app is for legacy url migration
 const APP_ID_OPENSEARCH_DASHBOARDS = 'kibana';
 const APP_LIST_FOR_READONLY_ROLE = [APP_ID_HOME, APP_ID_DASHBOARDS, APP_ID_OPENSEARCH_DASHBOARDS];
-
-export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPluginStart> {
+const GLOBAL_TENANT_RENDERING_TEXT = 'Global';
+const PRIVATE_TENANT_RENDERING_TEXT = 'Private';
+
+export class SecurityPlugin
+  implements
+    Plugin<
+      SecurityPluginSetup,
+      SecurityPluginStart,
+      SecurityPluginSetupDependencies,
+      SecurityPluginStartDependencies
+    > {
   // @ts-ignore : initializerContext not used
   constructor(private readonly initializerContext: PluginInitializerContext) {}
 
-  public async setup(core: CoreSetup): Promise<SecurityPluginSetup> {
+  public async setup(
+    core: CoreSetup,
+    deps: SecurityPluginSetupDependencies
+  ): Promise<SecurityPluginSetup> {
     const apiPermission = await hasApiPermission(core);
 
     const config = this.initializerContext.config.get<ClientConfigType>();
@@ -93,7 +108,7 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
           excludeFromDisabledTransportCategories(config.disabledTransportCategories.exclude);
           excludeFromDisabledRestCategories(config.disabledRestCategories.exclude);
 
-          return renderApp(coreStart, depsStart as AppPluginStartDependencies, params, config);
+          return renderApp(coreStart, depsStart as SecurityPluginStartDependencies, params, config);
         },
         category: {
           id: 'opensearch',
@@ -138,11 +153,17 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
       })
     );
 
+    if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) {
+      deps.savedObjectsManagement.columns.register(
+        (tenantColumn as unknown) as SavedObjectsManagementColumn<string>
+      );
+    }
+
     // Return methods that should be available to other plugins
     return {};
   }
 
-  public start(core: CoreStart): SecurityPluginStart {
+  public start(core: CoreStart, deps: SecurityPluginStartDependencies): SecurityPluginStart {
     const config = this.initializerContext.config.get<ClientConfigType>();
 
     setupTopNavButton(core, config);
diff --git a/public/types.ts b/public/types.ts
index 9e63ca632..8dd2ac2c4 100644
--- a/public/types.ts
+++ b/public/types.ts
@@ -14,14 +14,23 @@
  */
 
 import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
+import {
+  SavedObjectsManagementPluginSetup,
+  SavedObjectsManagementPluginStart,
+} from '../../../src/plugins/saved_objects_management/public';
 
 // eslint-disable-next-line @typescript-eslint/no-empty-interface
 export interface SecurityPluginSetup {}
 // eslint-disable-next-line @typescript-eslint/no-empty-interface
 export interface SecurityPluginStart {}
 
-export interface AppPluginStartDependencies {
+export interface SecurityPluginSetupDependencies {
+  savedObjectsManagement: SavedObjectsManagementPluginSetup;
+}
+
+export interface SecurityPluginStartDependencies {
   navigation: NavigationPublicPluginStart;
+  savedObjectsManagement: SavedObjectsManagementPluginStart;
 }
 
 export interface AuthInfo {
@@ -49,6 +58,7 @@ export interface ClientConfigType {
     backend_configurable: boolean;
   };
   multitenancy: {
+    enable_aggregation_view: boolean;
     enabled: boolean;
     tenants: {
       enable_private: boolean;
diff --git a/server/auth/types/authentication_type.ts b/server/auth/types/authentication_type.ts
index 2b1cfdf75..6d8274ce2 100755
--- a/server/auth/types/authentication_type.ts
+++ b/server/auth/types/authentication_type.ts
@@ -35,6 +35,7 @@ import {
   isValidTenant,
 } from '../../multitenancy/tenant_resolver';
 import { UnauthenticatedError } from '../../errors';
+import { GLOBAL_TENANT_SYMBOL } from '../../../public/apps/configuration/utils/tenant-utils';
 
 export interface IAuthenticationType {
   type: string;
@@ -50,6 +51,25 @@ export type IAuthHandlerConstructor = new (
   logger: Logger
 ) => IAuthenticationType;
 
+export interface OpenSearchAuthInfo {
+  user: string;
+  user_name: string;
+  user_requested_tenant: string;
+  remote_address: string;
+  backend_roles: string[];
+  custom_attribute_names: string[];
+  roles: string[];
+  tenants: Record<string, boolean>;
+  principal: string | null;
+  peer_certificates: string | null;
+  sso_logout_url: string | null;
+}
+
+export interface OpenSearchDashboardsAuthState {
+  authInfo?: OpenSearchAuthInfo;
+  selectedTenant?: string;
+}
+
 export abstract class AuthenticationType implements IAuthenticationType {
   protected static readonly ROUTES_TO_IGNORE: string[] = [
     '/api/core/capabilities', // FIXME: need to figureout how to bypass this API call
@@ -72,6 +92,7 @@ export abstract class AuthenticationType implements IAuthenticationType {
   ) {
     this.securityClient = new SecurityClient(esClient);
     this.type = '';
+    this.config = config;
   }
 
   public authHandler: AuthenticationHandler = async (request, response, toolkit) => {
@@ -80,6 +101,8 @@ export abstract class AuthenticationType implements IAuthenticationType {
       return toolkit.authenticated();
     }
 
+    const authState: OpenSearchDashboardsAuthState = {};
+
     // if browser request, auth logic is:
     //   1. check if request includes auth header or paramter(e.g. jwt in url params) is present, if so, authenticate with auth header.
     //   2. if auth header not present, check if auth cookie is present, if no cookie, send to authentication workflow
@@ -104,7 +127,7 @@ export abstract class AuthenticationType implements IAuthenticationType {
         }
 
         this.sessionStorageFactory.asScoped(request).set(cookie);
-      } catch (error) {
+      } catch (error: any) {
         return response.unauthorized({
           body: error.message,
         });
@@ -113,7 +136,7 @@ export abstract class AuthenticationType implements IAuthenticationType {
       // no auth header in request, try cookie
       try {
         cookie = await this.sessionStorageFactory.asScoped(request).get();
-      } catch (error) {
+      } catch (error: any) {
         this.logger.error(`Error parsing cookie: ${error.message}`);
         cookie = undefined;
       }
@@ -157,8 +180,15 @@ export abstract class AuthenticationType implements IAuthenticationType {
               'No available tenant for current user, please reach out to your system administrator',
           });
         }
+        authState.selectedTenant = tenant;
+
         // set tenant in header
-        Object.assign(authHeaders, { securitytenant: tenant });
+        if (this.config.multitenancy.enabled && this.config.multitenancy.enable_aggregation_view) {
+          // Store all saved objects in a single kibana index.
+          Object.assign(authHeaders, { securitytenant: GLOBAL_TENANT_SYMBOL });
+        } else {
+          Object.assign(authHeaders, { securitytenant: tenant });
+        }
 
         // set tenant to cookie
         if (tenant !== cookie!.tenant) {
@@ -177,9 +207,14 @@ export abstract class AuthenticationType implements IAuthenticationType {
         throw error;
       }
     }
+    if (!authInfo) {
+      authInfo = await this.securityClient.authinfo(request, authHeaders);
+    }
+    authState.authInfo = authInfo;
 
     return toolkit.authenticated({
       requestHeaders: authHeaders,
+      state: authState,
     });
   };
 
@@ -209,7 +244,7 @@ export abstract class AuthenticationType implements IAuthenticationType {
     if (!authInfo) {
       try {
         authInfo = await this.securityClient.authinfo(request, authHeader);
-      } catch (error) {
+      } catch (error: any) {
         throw new UnauthenticatedError(error);
       }
     }
diff --git a/server/index.ts b/server/index.ts
index bf1a2699d..99adb015b 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -104,6 +104,7 @@ export const configSchema = schema.object({
     show_roles: schema.boolean({ defaultValue: false }),
     enable_filter: schema.boolean({ defaultValue: false }),
     debug: schema.boolean({ defaultValue: false }),
+    enable_aggregation_view: schema.boolean({ defaultValue: false }),
     tenants: schema.object({
       enable_private: schema.boolean({ defaultValue: true }),
       enable_global: schema.boolean({ defaultValue: true }),
diff --git a/server/multitenancy/tenant_resolver.ts b/server/multitenancy/tenant_resolver.ts
index f99ce1d0c..281ab4cf6 100755
--- a/server/multitenancy/tenant_resolver.ts
+++ b/server/multitenancy/tenant_resolver.ts
@@ -17,9 +17,10 @@ import { isEmpty, findKey, cloneDeep } from 'lodash';
 import { OpenSearchDashboardsRequest } from '../../../../src/core/server';
 import { SecuritySessionCookie } from '../session/security_cookie';
 import { SecurityPluginConfigType } from '..';
-
-const PRIVATE_TENANT_SYMBOL: string = '__user__';
-const GLOBAL_TENANT_SYMBOL: string = '';
+import {
+  GLOBAL_TENANT_SYMBOL,
+  PRIVATE_TENANT_SYMBOL,
+} from '../../public/apps/configuration/utils/tenant-utils';
 
 export const PRIVATE_TENANTS: string[] = [PRIVATE_TENANT_SYMBOL, 'private'];
 export const GLOBAL_TENANTS: string[] = ['global', GLOBAL_TENANT_SYMBOL];
diff --git a/server/plugin.ts b/server/plugin.ts
index 8c04eb275..b7c9e0ce8 100644
--- a/server/plugin.ts
+++ b/server/plugin.ts
@@ -38,11 +38,15 @@ import {
   ISavedObjectTypeRegistry,
 } from '../../../src/core/server/saved_objects';
 import { setupIndexTemplate, migrateTenantIndices } from './multitenancy/tenant_index';
-import { IAuthenticationType } from './auth/types/authentication_type';
+import {
+  IAuthenticationType,
+  OpenSearchDashboardsAuthState,
+} from './auth/types/authentication_type';
 import { getAuthenticationHandler } from './auth/auth_handler_factory';
 import { setupMultitenantRoutes } from './multitenancy/routes';
 import { defineAuthTypeRoutes } from './routes/auth_type_routes';
 import { createMigrationOpenSearchClient } from '../../../src/core/server/saved_objects/migrations/core';
+import { SecuritySavedObjectsClientWrapper } from './saved_objects/saved_objects_wrapper';
 
 export interface SecurityPluginRequestContext {
   logger: Logger;
@@ -73,8 +77,11 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
   // @ts-ignore: property not initialzied in constructor
   private securityClient: SecurityClient;
 
+  private savedObjectClientWrapper: SecuritySavedObjectsClientWrapper;
+
   constructor(private readonly initializerContext: PluginInitializerContext) {
     this.logger = initializerContext.logger.get();
+    this.savedObjectClientWrapper = new SecuritySavedObjectsClientWrapper();
   }
 
   public async setup(core: CoreSetup) {
@@ -126,6 +133,14 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
       setupMultitenantRoutes(router, securitySessionStorageFactory, this.securityClient);
     }
 
+    if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) {
+      core.savedObjects.addClientWrapper(
+        2,
+        'security-saved-object-client-wrapper',
+        this.savedObjectClientWrapper.wrapperFactory
+      );
+    }
+
     return {
       config$,
       securityConfigClient: esClient,
@@ -135,8 +150,13 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
   // TODO: add more logs
   public async start(core: CoreStart) {
     this.logger.debug('opendistro_security: Started');
+
     const config$ = this.initializerContext.config.create<SecurityPluginConfigType>();
     const config = await config$.pipe(first()).toPromise();
+
+    this.savedObjectClientWrapper.httpStart = core.http;
+    this.savedObjectClientWrapper.config = config;
+
     if (config.multitenancy?.enabled) {
       const globalConfig$: Observable<SharedGlobalConfig> = this.initializerContext.config.legacy
         .globalConfig$;
@@ -161,6 +181,7 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
     }
 
     return {
+      http: core.http,
       es: core.opensearch.legacy,
     };
   }
diff --git a/server/saved_objects/saved_objects_wrapper.ts b/server/saved_objects/saved_objects_wrapper.ts
new file mode 100644
index 000000000..0cf767ebe
--- /dev/null
+++ b/server/saved_objects/saved_objects_wrapper.ts
@@ -0,0 +1,219 @@
+/*
+ *   Copyright OpenSearch Contributors
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License").
+ *   You may not use this file except in compliance with the License.
+ *   A copy of the License is located at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   or in the "license" file accompanying this file. This file is distributed
+ *   on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ *   express or implied. See the License for the specific language governing
+ *   permissions and limitations under the License.
+ */
+
+import _ from 'lodash';
+import {
+  HttpServiceStart,
+  SavedObject,
+  SavedObjectsBaseOptions,
+  SavedObjectsBulkCreateObject,
+  SavedObjectsBulkGetObject,
+  SavedObjectsBulkResponse,
+  SavedObjectsBulkUpdateObject,
+  SavedObjectsBulkUpdateOptions,
+  SavedObjectsBulkUpdateResponse,
+  SavedObjectsCheckConflictsObject,
+  SavedObjectsCheckConflictsResponse,
+  SavedObjectsClientWrapperFactory,
+  SavedObjectsCreateOptions,
+  SavedObjectsDeleteOptions,
+  SavedObjectsFindOptions,
+  SavedObjectsFindResponse,
+  SavedObjectsUpdateOptions,
+  SavedObjectsUpdateResponse,
+} from 'opensearch-dashboards/server';
+import { Config } from 'packages/osd-config/target';
+import { SecurityPluginConfigType } from '..';
+import {
+  DEFAULT_TENANT,
+  globalTenantName,
+  GLOBAL_TENANT_SYMBOL,
+  isPrivateTenant,
+  PRIVATE_TENANT_SYMBOL,
+} from '../../public/apps/configuration/utils/tenant-utils';
+import { OpenSearchDashboardsAuthState } from '../auth/types/authentication_type';
+
+export class SecuritySavedObjectsClientWrapper {
+  public httpStart?: HttpServiceStart;
+  public config?: SecurityPluginConfigType;
+
+  constructor() {}
+
+  public wrapperFactory: SavedObjectsClientWrapperFactory = (wrapperOptions) => {
+    const state: OpenSearchDashboardsAuthState =
+      (this.httpStart!.auth.get(wrapperOptions.request).state as OpenSearchDashboardsAuthState) ||
+      {};
+
+    const selectedTenant = state.selectedTenant;
+    const username = state.authInfo?.user_name;
+    const isGlobalEnabled = this.config!.multitenancy.tenants.enable_global;
+    const isPrivateEnabled = this.config!.multitenancy.tenants.enable_private;
+
+    let namespaceValue = selectedTenant;
+
+    const createWithNamespace = async <T = unknown>(
+      type: string,
+      attributes: T,
+      options?: SavedObjectsCreateOptions
+    ) => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.create(type, attributes, options);
+    };
+
+    const bulkGetWithNamespace = async <T = unknown>(
+      objects: SavedObjectsBulkGetObject[] = [],
+      options: SavedObjectsBaseOptions = {}
+    ): Promise<SavedObjectsBulkResponse<T>> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.bulkGet(objects, options);
+    };
+
+    const findWithNamespace = async <T = unknown>(
+      options: SavedObjectsFindOptions
+    ): Promise<SavedObjectsFindResponse<T>> => {
+      const tenants = state.authInfo?.tenants;
+      const availableTenantNames = Object.keys(tenants!);
+      availableTenantNames.push(DEFAULT_TENANT); // The value of namespace is "default" if saved objects are created when opensearch_security.multitenancy.enable_aggregation_view is set to false. So adding it to find.
+      if (isGlobalEnabled) {
+        availableTenantNames.push(GLOBAL_TENANT_SYMBOL);
+      }
+      if (isPrivateEnabled) {
+        availableTenantNames.push(PRIVATE_TENANT_SYMBOL + username);
+      }
+      if (availableTenantNames.includes(globalTenantName)) {
+        let index = availableTenantNames.indexOf(globalTenantName);
+        if (index > -1) {
+          availableTenantNames.splice(index, 1);
+        }
+        index = availableTenantNames.indexOf(username!);
+        if (index > -1) {
+          availableTenantNames.splice(index, 1);
+        }
+      }
+      const typeToNamespacesMap: any = {};
+      if (isPrivateTenant(selectedTenant!)) {
+        namespaceValue = selectedTenant! + username;
+      }
+      const searchTypes = Array.isArray(options.type) ? options.type : [options.type];
+      searchTypes.forEach((t) => {
+        if ('namespaces' in options) {
+          typeToNamespacesMap[t] = options.namespaces;
+        } else {
+          typeToNamespacesMap[t] = availableTenantNames;
+        }
+      });
+      if ('config' in typeToNamespacesMap) {
+        typeToNamespacesMap.config = [namespaceValue];
+      }
+      options.typeToNamespacesMap = new Map(Object.entries(typeToNamespacesMap));
+      options.type = '';
+      options.namespaces = [];
+
+      return await wrapperOptions.client.find(options);
+    };
+
+    const getWithNamespace = async <T = unknown>(
+      type: string,
+      id: string,
+      options: SavedObjectsBaseOptions = {}
+    ): Promise<SavedObject<T>> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.get(type, id, options);
+    };
+
+    const updateWithNamespace = async <T = unknown>(
+      type: string,
+      id: string,
+      attributes: Partial<T>,
+      options: SavedObjectsUpdateOptions = {}
+    ): Promise<SavedObjectsUpdateResponse<T>> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.update(type, id, attributes, options);
+    };
+
+    const bulkCreateWithNamespace = async <T = unknown>(
+      objects: Array<SavedObjectsBulkCreateObject<T>>,
+      options?: SavedObjectsCreateOptions
+    ): Promise<SavedObjectsBulkResponse<T>> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.bulkCreate(objects, options);
+    };
+
+    const bulkUpdateWithNamespace = async <T = unknown>(
+      objects: Array<SavedObjectsBulkUpdateObject<T>>,
+      options?: SavedObjectsBulkUpdateOptions
+    ): Promise<SavedObjectsBulkUpdateResponse<T>> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.bulkUpdate(objects, options);
+    };
+
+    const deleteWithNamespace = async (
+      type: string,
+      id: string,
+      options: SavedObjectsDeleteOptions = {}
+    ) => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.delete(type, id, options);
+    };
+
+    const checkConflictsWithNamespace = async (
+      objects: SavedObjectsCheckConflictsObject[] = [],
+      options: SavedObjectsBaseOptions = {}
+    ): Promise<SavedObjectsCheckConflictsResponse> => {
+      namespaceValue = this.getNamespaceValue(selectedTenant, isPrivateEnabled, username);
+      _.assign(options, { namespace: [namespaceValue] });
+      return await wrapperOptions.client.checkConflicts(objects, options);
+    };
+
+    return {
+      ...wrapperOptions.client,
+      get: getWithNamespace,
+      update: updateWithNamespace,
+      bulkCreate: bulkCreateWithNamespace,
+      bulkGet: bulkGetWithNamespace,
+      bulkUpdate: bulkUpdateWithNamespace,
+      create: createWithNamespace,
+      delete: deleteWithNamespace,
+      errors: wrapperOptions.client.errors,
+      checkConflicts: checkConflictsWithNamespace,
+      addToNamespaces: wrapperOptions.client.addToNamespaces,
+      find: findWithNamespace,
+      deleteFromNamespaces: wrapperOptions.client.deleteFromNamespaces,
+    };
+  };
+
+  private isAPrivateTenant(selectedTenant: string | undefined, isPrivateEnabled: boolean) {
+    return selectedTenant !== undefined && isPrivateEnabled && isPrivateTenant(selectedTenant);
+  }
+
+  private getNamespaceValue(
+    selectedTenant: string | undefined,
+    isPrivateEnabled: boolean,
+    username: string | undefined
+  ) {
+    let namespaceValue = selectedTenant;
+    if (this.isAPrivateTenant(selectedTenant, isPrivateEnabled)) {
+      namespaceValue = selectedTenant! + username;
+    }
+    return namespaceValue;
+  }
+}