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

[Backport 2.4] OSD Saved Object Aggregation View #1187

Merged
merged 3 commits into from
Nov 3, 2022
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
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.4'
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.4'

- 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