Skip to content

Commit

Permalink
[Backport 2.x] OSD Saved Object Aggregation View (#1186)
Browse files Browse the repository at this point in the history
* Saved Object Aggregation View (#1146)
* Move tenant-related utils to common folder (#1184)
* [Saved Object Aggregation View] Use namespace registry to add tenant filter (#1169)

Signed-off-by: Chang Liu <[email protected]>
Signed-off-by: Ryan Liang <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Yan Zeng <[email protected]>

Co-authored-by: Ryan Liang <[email protected]>
Co-authored-by: Ryan Liang <[email protected]>
Co-authored-by: Craig Perkins <[email protected]>
Co-authored-by: Yan Zeng <[email protected]>
  • Loading branch information
5 people authored Nov 3, 2022
1 parent a8278c1 commit ccb3ccd
Show file tree
Hide file tree
Showing 21 changed files with 671 additions and 50 deletions.
131 changes: 131 additions & 0 deletions .github/workflows/cypress-test.yml
Original file line number Diff line number Diff line change
@@ -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/2.4.0/latest/linux/x64/tar/builds/opensearch/dist/opensearch-min-2.4.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/2.4.0/latest/linux/x64/tar/builds/opensearch/plugins/opensearch-security-2.4.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: '2.x'
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: '2.x'

- 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 }}
19 changes: 19 additions & 0 deletions common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export const API_AUTH_LOGOUT = '/auth/logout';

export const ERROR_MISSING_ROLE_PATH = '/missing-role';

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 globalTenantName = 'global_tenant';

export enum AuthType {
BASIC = 'basicauth',
OPEN_ID = 'openid',
Expand All @@ -47,3 +54,15 @@ export function isValidResourceName(resourceName: string): boolean {
const exp = new RegExp('[\\p{C}%]', 'u');
return !exp.test(resourceName) && resourceName.length > 0;
}

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;
}
2 changes: 1 addition & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "2.4.0.0",
"opensearchDashboardsVersion": "2.4.0",
"configPath": ["opensearch_security"],
"requiredPlugins": ["navigation"],
"requiredPlugins": ["navigation", "savedObjectsManagement"],
"server": true,
"ui": true
}
15 changes: 12 additions & 3 deletions public/apps/account/account-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand All @@ -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.`);
}
}
Expand Down
3 changes: 2 additions & 1 deletion public/apps/account/account-nav-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions public/apps/account/tenant-switch-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface TenantSwitchPanelProps {
handleClose: () => void;
handleSwitchAndClose: () => void;
config: ClientConfigType;
tenant: string;
}

const GLOBAL_TENANT_KEY_NAME = 'global_tenant';
Expand Down Expand Up @@ -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.
Expand All @@ -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[]) => {
Expand Down
18 changes: 14 additions & 4 deletions public/apps/account/test/account-app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -36,6 +36,7 @@ jest.mock('../utils', () => ({

jest.mock('../../configuration/utils/tenant-utils', () => ({
selectTenant: jest.fn(),
fetchCurrentTenant: jest.fn(),
}));

describe('Account app', () => {
Expand All @@ -47,6 +48,12 @@ describe('Account app', () => {
},
};

const mockConfig = {
multitenancy: {
enable_aggregation_view: true,
},
};

const mockAccountInfo = {
data: {
roles: {
Expand All @@ -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) => {
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions public/apps/configuration/configuration-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
}
Expand All @@ -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 */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function generateTenantPermissionPanels(
onChange={onValueChangeHandler('tenantPatterns')}
onCreateOption={onCreateOptionHandler('tenantPatterns')}
options={permisionOptionsSet}
id="roles-tenant-permission-box"
/>
</EuiFlexItem>
<EuiFlexItem style={{ maxWidth: '170px' }}>
Expand Down
Loading

0 comments on commit ccb3ccd

Please sign in to comment.