From ffd7bdc26555885da007c785ef8f1c43751202a3 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:32:43 +0200 Subject: [PATCH 1/8] Allow users with read access to view Integrations app --- .../public/applications/integrations/app.tsx | 77 +++---------------- .../epm/screens/detail/index.test.tsx | 13 ++++ .../sections/epm/screens/detail/index.tsx | 62 ++++++++++----- .../fleet/public/hooks/use_request/app.ts | 9 ++- x-pack/plugins/fleet/server/mocks/index.ts | 3 +- x-pack/plugins/fleet/server/plugin.ts | 21 +++-- .../plugins/fleet/server/routes/app/index.ts | 21 ++--- .../plugins/fleet/server/routes/epm/index.ts | 15 ++-- .../plugins/fleet/server/routes/security.ts | 8 ++ .../fleet/server/services/app_context.ts | 21 +++-- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../fleet_api_integration/apis/agents/list.ts | 57 +------------- .../apis/epm/bulk_upgrade.ts | 9 +++ .../fleet_api_integration/apis/epm/delete.ts | 10 +++ .../fleet_api_integration/apis/epm/get.ts | 9 +++ .../apis/epm/install_by_upload.ts | 13 ++++ .../fleet_api_integration/apis/epm/list.ts | 9 +++ .../test/fleet_api_integration/apis/index.js | 10 ++- .../fleet_api_integration/apis/test_users.ts | 63 +++++++++++++++ 20 files changed, 251 insertions(+), 183 deletions(-) create mode 100644 x-pack/test/fleet_api_integration/apis/test_users.ts diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index b10cef9d3ffe4..771b17ae8c3ee 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -7,12 +7,10 @@ import React, { memo, useEffect, useState } from 'react'; import type { AppMountParameters } from 'kibana/public'; -import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui'; +import { EuiErrorBoundary, EuiPortal } from '@elastic/eui'; import type { History } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; import useObservable from 'react-use/lib/useObservable'; import { @@ -49,29 +47,23 @@ const ErrorLayout = ({ children }: { children: JSX.Element }) => ( ); -const Panel = styled(EuiPanel)` - max-width: 500px; - margin-right: auto; - margin-left: auto; -`; - export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { useBreadcrumbs('integrations'); const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); - const [permissionsError, setPermissionsError] = useState(); const [isInitialized, setIsInitialized] = useState(false); const [initializationError, setInitializationError] = useState(null); useEffect(() => { (async () => { - setPermissionsError(undefined); setIsInitialized(false); setInitializationError(null); try { + // Attempt Fleet Setup if user has permissions, otherwise skip setIsPermissionsLoading(true); const permissionsResponse = await sendGetPermissionsCheck(); setIsPermissionsLoading(false); + if (permissionsResponse.data?.success) { try { const setupResponse = await sendSetup(); @@ -83,69 +75,20 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { } setIsInitialized(true); } else { - setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR'); + setIsInitialized(true); } - } catch (err) { - setPermissionsError('REQUEST_ERROR'); + } catch { + // If there's an error checking permissions, default to proceeding without running setup + // User will only have access to EPM endpoints if they actually have permission + setIsInitialized(true); } })(); }, []); - if (isPermissionsLoading || permissionsError) { + if (isPermissionsLoading) { return ( - {isPermissionsLoading ? ( - - ) : permissionsError === 'REQUEST_ERROR' ? ( - - } - error={i18n.translate('xpack.fleet.permissionsRequestErrorMessageDescription', { - defaultMessage: 'There was a problem checking Fleet permissions', - })} - /> - ) : ( - - - {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( - - ) : ( - - )} - - } - body={ -

- {permissionsError === 'MISSING_SUPERUSER_ROLE' ? ( - superuser }} - /> - ) : ( - - )} -

- } - /> -
- )} +
); } diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index d70b6c68016be..d442f8a13e27e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -11,6 +11,7 @@ import { act, cleanup } from '@testing-library/react'; import { INTEGRATIONS_ROUTING_PATHS, pagePathGetters } from '../../../../constants'; import type { + CheckPermissionsResponse, GetAgentPoliciesResponse, GetFleetStatusResponse, GetInfoResponse, @@ -23,6 +24,7 @@ import type { } from '../../../../../../../common/types/models'; import { agentPolicyRouteService, + appRoutesService, epmRouteService, fleetSetupRouteService, packagePolicyRouteService, @@ -260,6 +262,7 @@ interface EpmPackageDetailsResponseProvidersMock { fleetSetup: jest.MockedFunction<() => GetFleetStatusResponse>; packagePolicyList: jest.MockedFunction<() => GetPackagePoliciesResponse>; agentPolicyList: jest.MockedFunction<() => GetAgentPoliciesResponse>; + appCheckPermissions: jest.MockedFunction<() => CheckPermissionsResponse>; } const mockApiCalls = ( @@ -740,6 +743,10 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos }, }; + const appCheckPermissionsResponse: CheckPermissionsResponse = { + success: true, + }; + const mockedApiInterface: MockedApi = { waitForApi() { return new Promise((resolve) => { @@ -757,6 +764,7 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos fleetSetup: jest.fn().mockReturnValue(agentsSetupResponse), packagePolicyList: jest.fn().mockReturnValue(packagePoliciesResponse), agentPolicyList: jest.fn().mockReturnValue(agentPoliciesResponse), + appCheckPermissions: jest.fn().mockReturnValue(appCheckPermissionsResponse), }, }; @@ -792,6 +800,11 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos return mockedApiInterface.responseProvider.epmGetStats(); } + if (path === appRoutesService.getCheckPermissionsPath()) { + markApiCallAsHandled(); + return mockedApiInterface.responseProvider.appCheckPermissions(); + } + const err = new Error(`API [GET ${path}] is not MOCKED!`); // eslint-disable-next-line no-console console.error(err); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 82436eb4d3f51..1c1d53987051c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -30,6 +30,7 @@ import { useUIExtension, useBreadcrumbs, useStartServices, + usePermissionCheck, } from '../../../../hooks'; import { PLUGIN_ID, @@ -95,7 +96,8 @@ export function Detail() { const { getId: getAgentPolicyId } = useAgentPolicyContext(); const { pkgkey, panel } = useParams(); const { getHref } = useLink(); - const hasWriteCapabilites = useCapabilities().write; + const hasWriteCapabilities = useCapabilities().write; + const permissionCheck = usePermissionCheck(); const history = useHistory(); const { pathname, search, hash } = useLocation(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); @@ -127,9 +129,11 @@ export function Detail() { const { data: packageInfoData, error: packageInfoError, - isLoading, + isLoading: packageInfoLoading, } = useGetPackageInfoByKey(pkgkey); + const isLoading = packageInfoLoading || permissionCheck.isLoading; + const showCustomTab = useUIExtension(packageInfoData?.response.name ?? '', 'package-detail-custom') !== undefined; @@ -330,7 +334,7 @@ export function Detail() { // eslint-disable-next-line @elastic/eui/href-or-on-click - ), - isSelected: panel === 'settings', - 'data-test-subj': `tab-settings`, - href: getHref('integration_details_settings', { - pkgkey: packageInfoKey, - ...(integration ? { integration } : {}), - }), - }); + if (hasWriteCapabilities && permissionCheck.data?.success) { + tabs.push({ + id: 'settings', + name: ( + + ), + isSelected: panel === 'settings', + 'data-test-subj': `tab-settings`, + href: getHref('integration_details_settings', { + pkgkey: packageInfoKey, + ...(integration ? { integration } : {}), + }), + }); + } if (showCustomTab) { tabs.push({ @@ -478,7 +488,17 @@ export function Detail() { } return tabs; - }, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab, CustomAssets]); + }, [ + packageInfo, + panel, + getHref, + integration, + hasWriteCapabilities, + permissionCheck.data?.success, + packageInstallStatus, + CustomAssets, + showCustomTab, + ]); return ( { return sendRequest({ @@ -23,3 +23,10 @@ export const sendGenerateServiceToken = () => { method: 'post', }); }; + +export const usePermissionCheck = () => { + return useRequest({ + path: appRoutesService.getCheckPermissionsPath(), + method: 'get', + }); +}; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 43b455045e72b..0e7b335da6775 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -37,7 +37,8 @@ export const createAppContextStartContractMock = (): FleetAppContext => { data: dataPluginMock.createStartContract(), encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(), savedObjects: savedObjectsServiceMock.createStartContract(), - security: securityMock.createStart(), + securitySetup: securityMock.createSetup(), + securityStart: securityMock.createStart(), logger: loggingSystemMock.create().get(), isProductionMode: true, configInitialValue: { diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 6aad028666ee8..c38d41d5caa5e 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -103,7 +103,8 @@ export interface FleetAppContext { data: DataPluginStart; encryptedSavedObjectsStart?: EncryptedSavedObjectsPluginStart; encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; - security?: SecurityPluginStart; + securitySetup?: SecurityPluginSetup; + securityStart?: SecurityPluginStart; config$?: Observable; configInitialValue: FleetConfigType; savedObjects: SavedObjectsServiceStart; @@ -164,14 +165,15 @@ export class FleetPlugin private licensing$!: Observable; private config$: Observable; private configInitialValue: FleetConfigType; - private cloud: CloudSetup | undefined; - private logger: Logger | undefined; + private cloud?: CloudSetup; + private logger?: Logger; private isProductionMode: FleetAppContext['isProductionMode']; private kibanaVersion: FleetAppContext['kibanaVersion']; private kibanaBranch: FleetAppContext['kibanaBranch']; - private httpSetup: HttpServiceSetup | undefined; - private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; + private httpSetup?: HttpServiceSetup; + private securitySetup?: SecurityPluginSetup; + private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; constructor(private readonly initializerContext: PluginInitializerContext) { this.config$ = this.initializerContext.config.create(); @@ -187,6 +189,7 @@ export class FleetPlugin this.licensing$ = deps.licensing.license$; this.encryptedSavedObjectsSetup = deps.encryptedSavedObjects; this.cloud = deps.cloud; + this.securitySetup = deps.security; const config = this.configInitialValue; registerSavedObjects(core.savedObjects, deps.encryptedSavedObjects); @@ -233,6 +236,10 @@ export class FleetPlugin // Always register app routes for permissions checking registerAppRoutes(router); + // Allow read-only users access to endpoints necessary for Integrations UI + // Only some endpoints require superuser so we pass a raw IRouter here + registerEPMRoutes(router); + // For all the routes we enforce the user to have role superuser const routerSuperuserOnly = makeRouterEnforcingSuperuser(router); // Register rest of routes only if security is enabled @@ -243,7 +250,6 @@ export class FleetPlugin registerOutputRoutes(routerSuperuserOnly); registerSettingsRoutes(routerSuperuserOnly); registerDataStreamRoutes(routerSuperuserOnly); - registerEPMRoutes(routerSuperuserOnly); registerPreconfigurationRoutes(routerSuperuserOnly); // Conditional config routes @@ -260,7 +266,8 @@ export class FleetPlugin data: plugins.data, encryptedSavedObjectsStart: plugins.encryptedSavedObjects, encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup, - security: plugins.security, + securitySetup: this.securitySetup, + securityStart: plugins.security, configInitialValue: this.configInitialValue, config$: this.config$, savedObjects: core.savedObjects, diff --git a/x-pack/plugins/fleet/server/routes/app/index.ts b/x-pack/plugins/fleet/server/routes/app/index.ts index f2fc6302c8ce5..6305b3e47237a 100644 --- a/x-pack/plugins/fleet/server/routes/app/index.ts +++ b/x-pack/plugins/fleet/server/routes/app/index.ts @@ -14,26 +14,21 @@ import { defaultIngestErrorHandler, GenerateServiceTokenError } from '../../erro export const getCheckPermissionsHandler: RequestHandler = async (context, request, response) => { const body: CheckPermissionsResponse = { success: true }; - try { - const security = await appContextService.getSecurity(); + + if (!appContextService.hasSecurity() || !appContextService.getSecurityLicense().isEnabled()) { + body.success = false; + body.error = 'MISSING_SECURITY'; + } else { + const security = appContextService.getSecurity(); const user = security.authc.getCurrentUser(request); if (!user?.roles.includes('superuser')) { body.success = false; body.error = 'MISSING_SUPERUSER_ROLE'; - return response.ok({ - body, - }); } - - return response.ok({ body: { success: true } }); - } catch (e) { - body.success = false; - body.error = 'MISSING_SECURITY'; - return response.ok({ - body, - }); } + + return response.ok({ body }); }; export const generateServiceTokenHandler: RequestHandler = async (context, request, response) => { diff --git a/x-pack/plugins/fleet/server/routes/epm/index.ts b/x-pack/plugins/fleet/server/routes/epm/index.ts index 684547dc1862c..0f49b3cfa772d 100644 --- a/x-pack/plugins/fleet/server/routes/epm/index.ts +++ b/x-pack/plugins/fleet/server/routes/epm/index.ts @@ -20,6 +20,7 @@ import { GetStatsRequestSchema, UpdatePackageRequestSchema, } from '../../types'; +import { enforceSuperUser } from '../security'; import { getCategoriesHandler, @@ -60,7 +61,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.LIMITED_LIST_PATTERN, validate: false, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getLimitedListHandler ); @@ -69,7 +70,7 @@ export const registerRoutes = (router: IRouter) => { { path: EPM_API_ROUTES.STATS_PATTERN, validate: GetStatsRequestSchema, - options: { tags: [`access:${PLUGIN_ID}`] }, + options: { tags: [`access:${PLUGIN_ID}-read`] }, }, getStatsHandler ); @@ -98,7 +99,7 @@ export const registerRoutes = (router: IRouter) => { validate: UpdatePackageRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - updatePackageHandler + enforceSuperUser(updatePackageHandler) ); router.post( @@ -107,7 +108,7 @@ export const registerRoutes = (router: IRouter) => { validate: InstallPackageFromRegistryRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - installPackageFromRegistryHandler + enforceSuperUser(installPackageFromRegistryHandler) ); router.post( @@ -116,7 +117,7 @@ export const registerRoutes = (router: IRouter) => { validate: BulkUpgradePackagesFromRegistryRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - bulkInstallPackagesFromRegistryHandler + enforceSuperUser(bulkInstallPackagesFromRegistryHandler) ); router.post( @@ -132,7 +133,7 @@ export const registerRoutes = (router: IRouter) => { }, }, }, - installPackageByUploadHandler + enforceSuperUser(installPackageByUploadHandler) ); router.delete( @@ -141,6 +142,6 @@ export const registerRoutes = (router: IRouter) => { validate: DeletePackageRequestSchema, options: { tags: [`access:${PLUGIN_ID}-all`] }, }, - deletePackageHandler + enforceSuperUser(deletePackageHandler) ); }; diff --git a/x-pack/plugins/fleet/server/routes/security.ts b/x-pack/plugins/fleet/server/routes/security.ts index 60011dcf3d33f..8efea34ed8164 100644 --- a/x-pack/plugins/fleet/server/routes/security.ts +++ b/x-pack/plugins/fleet/server/routes/security.ts @@ -13,6 +13,14 @@ export function enforceSuperUser( handler: RequestHandler ): RequestHandler { return function enforceSuperHandler(context, req, res) { + if (!appContextService.hasSecurity() || !appContextService.getSecurityLicense().isEnabled()) { + return res.forbidden({ + body: { + message: `Access to this API requires that security is enabled`, + }, + }); + } + const security = appContextService.getSecurity(); const user = security.authc.getCurrentUser(req); if (!user) { diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 1fb34a9a399eb..a1e6ef4545aef 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -22,7 +22,7 @@ import type { EncryptedSavedObjectsPluginSetup, } from '../../../encrypted_saved_objects/server'; -import type { SecurityPluginStart } from '../../../security/server'; +import type { SecurityPluginStart, SecurityPluginSetup } from '../../../security/server'; import type { FleetConfigType } from '../../common'; import type { ExternalCallback, @@ -39,7 +39,8 @@ class AppContextService { private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; private data: DataPluginStart | undefined; private esClient: ElasticsearchClient | undefined; - private security: SecurityPluginStart | undefined; + private securitySetup: SecurityPluginSetup | undefined; + private securityStart: SecurityPluginStart | undefined; private config$?: Observable; private configSubject$?: BehaviorSubject; private savedObjects: SavedObjectsServiceStart | undefined; @@ -56,7 +57,8 @@ class AppContextService { this.esClient = appContext.elasticsearch.client.asInternalUser; this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient(); this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup; - this.security = appContext.security; + this.securitySetup = appContext.securitySetup; + this.securityStart = appContext.securityStart; this.savedObjects = appContext.savedObjects; this.isProductionMode = appContext.isProductionMode; this.cloud = appContext.cloud; @@ -92,14 +94,21 @@ class AppContextService { } public getSecurity() { - if (!this.security) { + if (!this.hasSecurity()) { throw new Error('Security service not set.'); } - return this.security; + return this.securityStart!; + } + + public getSecurityLicense() { + if (!this.hasSecurity()) { + throw new Error('Security service not set.'); + } + return this.securitySetup!.license; } public hasSecurity() { - return !!this.security; + return !!this.securitySetup && !!this.securityStart; } public getCloud() { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 642668bcecf34..297b0c2fe7bf3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10879,8 +10879,6 @@ "xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "最新バージョンに更新", "xpack.fleet.integrationsAppTitle": "統合", "xpack.fleet.integrationsHeaderTitle": "Elasticエージェント統合", - "xpack.fleet.integrationsPermissionDeniedErrorMessage": "統合にアクセスする権限がありません。統合には{roleName}権限が必要です。", - "xpack.fleet.integrationsSecurityRequiredErrorMessage": "統合を使用するには、KibanaとElasticsearchでセキュリティを有効にする必要があります。", "xpack.fleet.invalidLicenseDescription": "現在のライセンスは期限切れです。登録されたビートエージェントは引き続き動作しますが、Elastic Fleet インターフェイスにアクセスするには有効なライセンスが必要です。", "xpack.fleet.invalidLicenseTitle": "ライセンスの期限切れ", "xpack.fleet.multiTextInput.addRow": "行の追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4d86994c0fb84..ed17eaffdf5cf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -10993,8 +10993,6 @@ "xpack.fleet.integrations.updatePackage.updatePackageButtonLabel": "更新到最新版本", "xpack.fleet.integrationsAppTitle": "集成", "xpack.fleet.integrationsHeaderTitle": "Elastic 代理集成", - "xpack.fleet.integrationsPermissionDeniedErrorMessage": "您无权访问“集成”。“集成”需要 {roleName} 权限。", - "xpack.fleet.integrationsSecurityRequiredErrorMessage": "必须在 Kibana 和 Elasticsearch 中启用安全性,才能使用“集成”。", "xpack.fleet.invalidLicenseDescription": "您当前的许可证已过期。已注册 Beats 代理将继续工作,但您需要有效的许可证,才能访问 Elastic Fleet 界面。", "xpack.fleet.invalidLicenseTitle": "已过期许可证", "xpack.fleet.multiTextInput.addRow": "添加行", diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index a11f4d49fe0f1..3795734f60fe0 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -8,66 +8,15 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { testUsers } from '../test_users'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const supertest = getService('supertest'); - const security = getService('security'); - const users: { [rollName: string]: { username: string; password: string; permissions?: any } } = { - kibana_basic_user: { - permissions: { - feature: { - dashboards: ['read'], - }, - spaces: ['*'], - }, - username: 'kibana_basic_user', - password: 'changeme', - }, - fleet_user: { - permissions: { - feature: { - fleet: ['read'], - }, - spaces: ['*'], - }, - username: 'fleet_user', - password: 'changeme', - }, - fleet_admin: { - permissions: { - feature: { - fleet: ['all'], - }, - spaces: ['*'], - }, - username: 'fleet_admin', - password: 'changeme', - }, - }; describe('fleet_list_agent', () => { before(async () => { - for (const roleName in users) { - if (users.hasOwnProperty(roleName)) { - const user = users[roleName]; - - if (user.permissions) { - await security.role.create(roleName, { - kibana: [user.permissions], - }); - } - - // Import a repository first - await security.user.create(user.username, { - password: user.password, - roles: [roleName], - full_name: user.username, - }); - } - } - await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents'); }); after(async () => { @@ -77,13 +26,13 @@ export default function ({ getService }: FtrProviderContext) { it('should return a 403 if a user without the superuser role try to access the APU', async () => { await supertestWithoutAuth .get(`/api/fleet/agents`) - .auth(users.fleet_admin.username, users.fleet_admin.password) + .auth(testUsers.fleet_all.username, testUsers.fleet_all.password) .expect(403); }); it('should not return the list of agents when requesting as a user without fleet permissions', async () => { await supertestWithoutAuth .get(`/api/fleet/agents`) - .auth(users.kibana_basic_user.username, users.kibana_basic_user.password) + .auth(testUsers.fleet_no_access.username, testUsers.fleet_no_access.password) .expect(403); }); diff --git a/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts b/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts index bb75629e222a5..3b3ccb03e56f3 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/bulk_upgrade.ts @@ -14,10 +14,12 @@ import { IBulkInstallPackageHTTPError, } from '../../../../plugins/fleet/common'; import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const deletePackage = async (pkgkey: string) => { await supertest.delete(`/api/fleet/epm/packages/${pkgkey}`).set('kbn-xsrf', 'xxxx'); @@ -44,6 +46,13 @@ export default function (providerContext: FtrProviderContext) { it('should return 400 if no packages are requested for upgrade', async function () { await supertest.post(`/api/fleet/epm/packages/_bulk`).set('kbn-xsrf', 'xxxx').expect(400); }); + it('should return 403 if read only user requests upgrade', async function () { + await supertestWithoutAuth + .post(`/api/fleet/epm/packages/_bulk`) + .auth(testUsers.fleet_read_only.username, testUsers.fleet_read_only.password) + .set('kbn-xsrf', 'xxxx') + .expect(403); + }); it('should return 200 and an array for upgrading a package', async function () { const { body }: { body: BulkInstallPackagesResponse } = await supertest .post(`/api/fleet/epm/packages/_bulk`) diff --git a/x-pack/test/fleet_api_integration/apis/epm/delete.ts b/x-pack/test/fleet_api_integration/apis/epm/delete.ts index 40650c4c176f7..9980d85ac171e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/delete.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/delete.ts @@ -8,10 +8,12 @@ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const requiredPackage = 'elastic_agent-0.0.7'; const installPackage = async (pkgkey: string) => { @@ -52,5 +54,13 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }) .expect(200); }); + + it('should return 403 for read-only users', async () => { + await supertestWithoutAuth + .delete(`/api/fleet/epm/packages/${requiredPackage}`) + .auth(testUsers.fleet_read_only.username, testUsers.fleet_read_only.password) + .set('kbn-xsrf', 'xxxx') + .expect(403); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts index 014fe0808d255..13533a9a82af0 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts @@ -11,11 +11,13 @@ import path from 'path'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const testPkgKey = 'apache-0.1.4'; @@ -91,5 +93,12 @@ export default function (providerContext: FtrProviderContext) { it('returns a 400 for a package key without a proper semver version', async function () { await supertest.get('/api/fleet/epm/packages/endpoint-0.1.0.1.2.3').expect(400); }); + + it('allows user with only read permission to access', async () => { + await supertestWithoutAuth + .get(`/api/fleet/epm/packages/${testPkgKey}`) + .auth(testUsers.fleet_read_only.username, testUsers.fleet_read_only.password) + .expect(200); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index 23feacbcee374..86928874f8a34 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -12,10 +12,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const dockerServers = getService('dockerServers'); const testPkgArchiveTgz = path.join( @@ -190,5 +192,16 @@ export default function (providerContext: FtrProviderContext) { '{"statusCode":400,"error":"Bad Request","message":"Name thisIsATypo and version 0.1.4 do not match top-level directory apache-0.1.4"}' ); }); + + it('should not allow users without all access', async () => { + const buf = fs.readFileSync(testPkgArchiveTgz); + await supertestWithoutAuth + .post(`/api/fleet/epm/packages`) + .auth(testUsers.fleet_read_only.username, testUsers.fleet_read_only.password) + .set('kbn-xsrf', 'xxxx') + .type('application/gzip') + .send(buf) + .expect(403); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/list.ts b/x-pack/test/fleet_api_integration/apis/epm/list.ts index 931e494798220..56f3e6ca1f2fa 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/list.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/list.ts @@ -9,10 +9,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; +import { testUsers } from '../test_users'; export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); // use function () {} and not () => {} here @@ -54,6 +56,13 @@ export default function (providerContext: FtrProviderContext) { expect(listResponse.response).to.eql(['endpoint']); }); + + it('allows user with only read permission to access', async () => { + await supertestWithoutAuth + .get('/api/fleet/epm/packages') + .auth(testUsers.fleet_read_only.username, testUsers.fleet_read_only.password) + .expect(200); + }); }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 387433b787728..fd5ac05247fd2 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -5,10 +5,16 @@ * 2.0. */ -export default function ({ loadTestFile }) { +import { setupTestUsers } from './test_users'; + +export default function ({ loadTestFile, getService }) { describe('Fleet Endpoints', function () { + before(async () => { + await setupTestUsers(getService('security')); + }); + // EPM - loadTestFile(require.resolve('./epm/index')); + loadTestFile(require.resolve('./epm')); // Fleet setup loadTestFile(require.resolve('./fleet_setup')); diff --git a/x-pack/test/fleet_api_integration/apis/test_users.ts b/x-pack/test/fleet_api_integration/apis/test_users.ts new file mode 100644 index 0000000000000..a1df6d3a31b71 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/test_users.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecurityService } from '../../../../test/common/services/security/security'; + +export const testUsers: { + [rollName: string]: { username: string; password: string; permissions?: any }; +} = { + fleet_no_access: { + permissions: { + feature: { + dashboards: ['read'], + }, + spaces: ['*'], + }, + username: 'fleet_no_access', + password: 'changeme', + }, + fleet_read_only: { + permissions: { + feature: { + fleet: ['read'], + }, + spaces: ['*'], + }, + username: 'fleet_read_only', + password: 'changeme', + }, + fleet_all: { + permissions: { + feature: { + fleet: ['all'], + }, + spaces: ['*'], + }, + username: 'fleet_all', + password: 'changeme', + }, +}; + +export const setupTestUsers = async (security: SecurityService) => { + for (const roleName in testUsers) { + if (testUsers.hasOwnProperty(roleName)) { + const user = testUsers[roleName]; + + if (user.permissions) { + await security.role.create(roleName, { + kibana: [user.permissions], + }); + } + + await security.user.create(user.username, { + password: user.password, + roles: [roleName], + full_name: user.username, + }); + } + } +}; From 0c7209f7ccc22442717b5b5d467c8607fc09a694 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Mon, 11 Oct 2021 18:43:49 +0200 Subject: [PATCH 2/8] Add UX for when security is disabled --- .../sections/epm/screens/detail/index.tsx | 83 +++++++++++++++---- .../fleet/public/components/header.tsx | 3 + .../plugins/fleet/storybook/context/http.ts | 4 + 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 1c1d53987051c..6d65451cf5450 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -8,10 +8,12 @@ import type { ReactEventHandler } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Redirect, Route, Switch, useLocation, useParams, useHistory } from 'react-router-dom'; import styled from 'styled-components'; +import type { EuiToolTipProps } from '@elastic/eui'; import { EuiBetaBadge, EuiButton, EuiButtonEmpty, + EuiCallOut, EuiDescriptionList, EuiDescriptionListDescription, EuiDescriptionListTitle, @@ -19,6 +21,7 @@ import { EuiFlexItem, EuiSpacer, EuiText, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -62,6 +65,7 @@ import { OverviewPage } from './overview'; import { PackagePoliciesPage } from './policies'; import { SettingsPage } from './settings'; import { CustomViewPage } from './custom'; + import './index.scss'; export interface DetailParams { @@ -98,6 +102,9 @@ export function Detail() { const { getHref } = useLink(); const hasWriteCapabilities = useCapabilities().write; const permissionCheck = usePermissionCheck(); + const missingSecurityConfiguration = + !permissionCheck.data?.success && permissionCheck.data?.error === 'MISSING_SECURITY'; + const userCanInstallIntegrations = hasWriteCapabilities && permissionCheck.data?.success; const history = useHistory(); const { pathname, search, hash } = useLocation(); const queryParams = useMemo(() => new URLSearchParams(search), [search]); @@ -331,10 +338,9 @@ export function Detail() { { isDivider: true }, { content: ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - + ) : ( + + ), + } + : undefined + } > - + ), }, ].map((item, index) => ( @@ -374,16 +397,17 @@ export function Detail() { ) : undefined, [ - getHref, - handleAddIntegrationPolicyClick, - hasWriteCapabilities, - integration, - integrationInfo, packageInfo, + updateAvailable, packageInstallStatus, + userCanInstallIntegrations, + getHref, pkgkey, - updateAvailable, + integration, agentPolicyIdFromContext, + handleAddIntegrationPolicyClick, + missingSecurityConfiguration, + integrationInfo?.title, ] ); @@ -411,11 +435,7 @@ export function Detail() { }, ]; - if ( - hasWriteCapabilities && - permissionCheck.data?.success && - packageInstallStatus === InstallStatus.installed - ) { + if (userCanInstallIntegrations && packageInstallStatus === InstallStatus.installed) { tabs.push({ id: 'policies', name: ( @@ -451,7 +471,7 @@ export function Detail() { }); } - if (hasWriteCapabilities && permissionCheck.data?.success) { + if (userCanInstallIntegrations) { tabs.push({ id: 'settings', name: ( @@ -493,18 +513,32 @@ export function Detail() { panel, getHref, integration, - hasWriteCapabilities, - permissionCheck.data?.success, + userCanInstallIntegrations, packageInstallStatus, CustomAssets, showCustomTab, ]); + const securityCallout = missingSecurityConfiguration ? ( + <> + + In order to fully use Fleet, you must enable Elasticsearch and Kibana security features. + Follow the steps in this guide to enable security. + + + + ) : undefined; + return ( @@ -546,3 +580,16 @@ export function Detail() { ); } + +type EuiButtonPropsFull = Parameters[0]; + +const EuiButtonWithTooltip: React.FC }> = + ({ tooltip: tooltipProps, ...buttonProps }) => { + return tooltipProps ? ( + + + + ) : ( + + ); + }; diff --git a/x-pack/plugins/fleet/public/components/header.tsx b/x-pack/plugins/fleet/public/components/header.tsx index 80cebe3b0b304..2a8b20240a4f6 100644 --- a/x-pack/plugins/fleet/public/components/header.tsx +++ b/x-pack/plugins/fleet/public/components/header.tsx @@ -43,6 +43,7 @@ export interface HeaderProps { leftColumn?: JSX.Element; rightColumn?: JSX.Element; rightColumnGrow?: EuiFlexItemProps['grow']; + topContent?: JSX.Element; tabs?: Array & { name?: JSX.Element | string }>; tabsClassName?: string; 'data-test-subj'?: string; @@ -61,6 +62,7 @@ export const Header: React.FC = ({ leftColumn, rightColumn, rightColumnGrow, + topContent, tabs, maxWidth, tabsClassName, @@ -68,6 +70,7 @@ export const Header: React.FC = ({ }) => ( + {topContent} { return await import('./fixtures/integration.okta'); } + if (path.startsWith('/api/fleet/check-permissions')) { + return { success: true }; + } + return {}; }) as HttpHandler, } as unknown as HttpStart; From 018dc910ca70f86bb3f66ae6170a7e7aadb8db9f Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:43:50 +0200 Subject: [PATCH 3/8] Clear asset cache before installing uploaded packages --- x-pack/plugins/fleet/server/services/epm/archive/cache.ts | 8 ++++++++ x-pack/plugins/fleet/server/services/epm/archive/index.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts index 7f479dc5d6b63..676c3aa571a0f 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts @@ -67,3 +67,11 @@ export const setPackageInfo = ({ }; export const deletePackageInfo = (args: SharedKey) => packageInfoCache.delete(sharedKey(args)); + +export const clearPackageFileCache = (args: SharedKey) => { + const fileList = getArchiveFilelist(args) ?? []; + fileList.forEach((filePath) => { + deleteArchiveEntry(filePath); + }); + deleteArchiveFilelist(args); +}; diff --git a/x-pack/plugins/fleet/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts index b08ec815a394d..7c590eb1bcd39 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/index.ts @@ -16,6 +16,7 @@ import { setArchiveFilelist, deleteArchiveFilelist, deletePackageInfo, + clearPackageFileCache, } from './cache'; import type { SharedKey } from './cache'; import { getBufferExtractor } from './extract'; @@ -42,6 +43,9 @@ export async function unpackBufferToCache({ archiveBuffer: Buffer; installSource: InstallSource; }): Promise { + // Make sure any buffers from previous installations from registry or upload are deleted first + clearPackageFileCache({ name, version }); + const entries = await unpackBufferEntries(archiveBuffer, contentType); const paths: string[] = []; entries.forEach((entry) => { From 27475524de237d6f3353a084ed08a8d5074ac594 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Wed, 13 Oct 2021 12:49:21 +0200 Subject: [PATCH 4/8] Fix i18n --- .../sections/epm/screens/detail/index.tsx | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 6d65451cf5450..bd6a1186292af 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -356,12 +356,12 @@ export function Detail() { ? { content: missingSecurityConfiguration ? ( ) : ( ), @@ -524,10 +524,28 @@ export function Detail() { + } > - In order to fully use Fleet, you must enable Elasticsearch and Kibana security features. - Follow the steps in this guide to enable security. + + + + ), + }} + /> From 93dabe293bdb816cf59e05c0209da33b220b0472 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Wed, 13 Oct 2021 12:49:37 +0200 Subject: [PATCH 5/8] Fix assets tab for read-only users --- .../epm/screens/detail/assets/assets.tsx | 23 +++++++++++++++++-- x-pack/plugins/fleet/server/plugin.ts | 2 ++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx index 6d075faeef308..a8d27580e0bd1 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/assets/assets.tsx @@ -9,6 +9,7 @@ import React, { useEffect, useState } from 'react'; import { Redirect } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; +import { groupBy } from 'lodash'; import { Loading, Error, ExtensionWrapper } from '../../../../../components'; @@ -67,8 +68,26 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => { id, type, })); - const { savedObjects } = await savedObjectsClient.bulkGet(objectsToGet); - setAssetsSavedObjects(savedObjects as AssetSavedObject[]); + + // We don't have an API to know which SO types a user has access to, so instead we make a request for each + // SO type and ignore the 403 errors + const objectsByType = await Promise.all( + Object.entries(groupBy(objectsToGet, 'type')).map(([type, objects]) => + savedObjectsClient + .bulkGet(objects) + // Ignore privilege errors + .catch((e: any) => { + if (e?.body?.statusCode === 403) { + return { savedObjects: [] }; + } else { + throw e; + } + }) + .then(({ savedObjects }) => savedObjects as AssetSavedObject[]) + ) + ); + + setAssetsSavedObjects(objectsByType.flat()); } catch (e) { setFetchError(e); } finally { diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index c38d41d5caa5e..a706ca6a54fdc 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -37,6 +37,7 @@ import { AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, + ASSETS_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, @@ -123,6 +124,7 @@ const allSavedObjectTypes = [ AGENT_POLICY_SAVED_OBJECT_TYPE, PACKAGE_POLICY_SAVED_OBJECT_TYPE, PACKAGES_SAVED_OBJECT_TYPE, + ASSETS_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, From 4d08eca637f831e8a2d39fe7cd60704f3559bc9c Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:01:58 +0200 Subject: [PATCH 6/8] Fix base path link --- .../integrations/sections/epm/screens/detail/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index bd6a1186292af..3431e697b5650 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -537,7 +537,7 @@ export function Detail() { Follow the {guideLink} to enable security." values={{ guideLink: ( - + Date: Wed, 13 Oct 2021 13:07:15 +0200 Subject: [PATCH 7/8] Fix i18n key typo --- .../integrations/sections/epm/screens/detail/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 3431e697b5650..ade290aab4e5e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -356,7 +356,7 @@ export function Detail() { ? { content: missingSecurityConfiguration ? ( ) : ( From 2a323624f3869314e84477c26a5bc41a461bf247 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Wed, 13 Oct 2021 14:13:03 +0000 Subject: [PATCH 8/8] Remove unused i18n key --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 995676a1156db..482a493865a47 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -10944,7 +10944,6 @@ "xpack.fleet.preconfiguration.missingIDError": "{agentPolicyName}には「id」フィールドがありません。ポリシーのis_defaultまたはis_default_fleet_serverに設定されている場合をのぞき、「id」は必須です。", "xpack.fleet.preconfiguration.packageMissingError": "{agentPolicyName}を追加できませんでした。{pkgName}がインストールされていません。{pkgName}を`{packagesConfigValue}`に追加するか、{packagePolicyName}から削除してください。", "xpack.fleet.preconfiguration.policyDeleted": "構成済みのポリシー{id}が削除されました。作成をスキップしています", - "xpack.fleet.securityRequiredErrorTitle": "セキュリティが有効ではありません", "xpack.fleet.serverError.agentPolicyDoesNotExist": "エージェントポリシー{agentPolicyId}が存在しません", "xpack.fleet.serverError.enrollmentKeyDuplicate": "エージェントポリシーの{agentPolicyId}登録キー{providedKeyName}はすでに存在します", "xpack.fleet.serverError.returnedIncorrectKey": "find enrollmentKeyByIdで正しくないキーが返されました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 115b0a346e789..2322f6213149f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -11058,7 +11058,6 @@ "xpack.fleet.preconfiguration.missingIDError": "{agentPolicyName} 缺失 `id` 字段。`id` 是必需的,但标记为 is_default 或 is_default_fleet_server 的策略除外。", "xpack.fleet.preconfiguration.packageMissingError": "{agentPolicyName} 无法添加。{pkgName} 未安装,请将 {pkgName} 添加到 `{packagesConfigValue}` 或将其从 {packagePolicyName} 中移除。", "xpack.fleet.preconfiguration.policyDeleted": "预配置的策略 {id} 已删除;将跳过创建", - "xpack.fleet.securityRequiredErrorTitle": "安全性未启用", "xpack.fleet.serverError.agentPolicyDoesNotExist": "代理策略 {agentPolicyId} 不存在", "xpack.fleet.serverError.enrollmentKeyDuplicate": "称作 {providedKeyName} 的注册密钥对于代理策略 {agentPolicyId} 已存在", "xpack.fleet.serverError.returnedIncorrectKey": "find enrollmentKeyById 返回错误的密钥",