Skip to content

Commit

Permalink
[Fleet] Allow packages to specify index privileges (elastic#112397)
Browse files Browse the repository at this point in the history
* tidy: move default index privs to const

* add index privileges to package policy SO schema

* add default index privileges const

* add privileges to epm schema

* add privileges to input stream types

* use new const for default index privileges

* permissions being added to policy

* fix unit tests

* add note about export

* tidy: move default index privs to const

* add index privileges to package policy SO schema

* add default index privileges const

* add privileges to epm schema

* add privileges to input stream types

* use new const for default index privileges

* permissions being added to policy

* fix unit tests

* add note about export

* remove outdated tests

* return enabled check to start of function

* add privileges to SO mapping

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
hop-dev and kibanamachine committed Sep 20, 2021
1 parent c16480e commit 75c6dfa
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 35 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/constants/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
*/

export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';

export const PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES = ['auto_configure', 'create_doc'];
5 changes: 2 additions & 3 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ export enum RegistryDataStreamKeys {
ingest_pipeline = 'ingest_pipeline',
elasticsearch = 'elasticsearch',
dataset_is_prefix = 'dataset_is_prefix',
permissions = 'permissions',
}

export interface RegistryDataStream {
Expand All @@ -297,15 +296,15 @@ export interface RegistryDataStream {
[RegistryDataStreamKeys.ingest_pipeline]?: string;
[RegistryDataStreamKeys.elasticsearch]?: RegistryElasticsearch;
[RegistryDataStreamKeys.dataset_is_prefix]?: boolean;
[RegistryDataStreamKeys.permissions]?: RegistryDataStreamPermissions;
}

export interface RegistryElasticsearch {
privileges?: RegistryDataStreamPrivileges;
'index_template.settings'?: estypes.IndicesIndexSettings;
'index_template.mappings'?: estypes.MappingTypeMapping;
}

export interface RegistryDataStreamPermissions {
export interface RegistryDataStreamPrivileges {
cluster?: string[];
indices?: string[];
}
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/fleet/common/types/models/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface NewPackagePolicyInputStream {
data_stream: {
dataset: string;
type: string;
elasticsearch?: {
privileges?: {
indices?: string[];
};
};
};
vars?: PackagePolicyConfigRecord;
config?: PackagePolicyConfigRecord;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export {
DEFAULT_FLEET_SERVER_AGENT_POLICY,
DEFAULT_OUTPUT,
DEFAULT_PACKAGES,
PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
// Fleet Server index
FLEET_SERVER_SERVERS_INDEX,
ENROLLMENT_API_KEYS_INDEX,
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ const getSavedObjectTypes = (
properties: {
dataset: { type: 'keyword' },
type: { type: 'keyword' },
elasticsearch: {
properties: {
privileges: { type: 'flattened' },
},
},
},
},
vars: { type: 'flattened' },
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
AGENT_POLICY_SAVED_OBJECT_TYPE,
AGENT_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
} from '../constants';
import type {
PackagePolicy,
Expand Down Expand Up @@ -825,7 +826,7 @@ class AgentPolicyService {
permissions._elastic_agent_checks.indices = [
{
names,
privileges: ['auto_configure', 'create_doc'],
privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
},
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { PackagePolicy, RegistryDataStream } from '../types';

import { getPackageInfo } from './epm/packages';
import {
getDataStreamPermissions,
getDataStreamPrivileges,
storedPackagePoliciesToAgentPermissions,
} from './package_policies_to_agent_permissions';

Expand Down Expand Up @@ -380,22 +380,22 @@ describe('storedPackagePoliciesToAgentPermissions()', () => {
});
});

describe('getDataStreamPermissions()', () => {
it('returns defaults for a datastream with no permissions', () => {
describe('getDataStreamPrivileges()', () => {
it('returns defaults for a datastream with no privileges', () => {
const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream;
const permissions = getDataStreamPermissions(dataStream);
const privileges = getDataStreamPrivileges(dataStream);

expect(permissions).toMatchObject({
expect(privileges).toMatchObject({
names: ['logs-test-*'],
privileges: ['auto_configure', 'create_doc'],
});
});

it('adds the namespace to the index name', () => {
const dataStream = { type: 'logs', dataset: 'test' } as RegistryDataStream;
const permissions = getDataStreamPermissions(dataStream, 'namespace');
const privileges = getDataStreamPrivileges(dataStream, 'namespace');

expect(permissions).toMatchObject({
expect(privileges).toMatchObject({
names: ['logs-test-namespace'],
privileges: ['auto_configure', 'create_doc'],
});
Expand All @@ -407,9 +407,9 @@ describe('getDataStreamPermissions()', () => {
dataset: 'test',
dataset_is_prefix: true,
} as RegistryDataStream;
const permissions = getDataStreamPermissions(dataStream, 'namespace');
const privileges = getDataStreamPrivileges(dataStream, 'namespace');

expect(permissions).toMatchObject({
expect(privileges).toMatchObject({
names: ['logs-test.*-namespace'],
privileges: ['auto_configure', 'create_doc'],
});
Expand All @@ -421,25 +421,27 @@ describe('getDataStreamPermissions()', () => {
dataset: 'test',
hidden: true,
} as RegistryDataStream;
const permissions = getDataStreamPermissions(dataStream, 'namespace');
const privileges = getDataStreamPrivileges(dataStream, 'namespace');

expect(permissions).toMatchObject({
expect(privileges).toMatchObject({
names: ['.logs-test-namespace'],
privileges: ['auto_configure', 'create_doc'],
});
});

it('uses custom permissions if they are present in the datastream', () => {
it('uses custom privileges if they are present in the datastream', () => {
const dataStream = {
type: 'logs',
dataset: 'test',
permissions: { indices: ['read', 'write'] },
elasticsearch: {
privileges: { indices: ['read', 'monitor'] },
},
} as RegistryDataStream;
const permissions = getDataStreamPermissions(dataStream, 'namespace');
const privileges = getDataStreamPrivileges(dataStream, 'namespace');

expect(permissions).toMatchObject({
expect(privileges).toMatchObject({
names: ['logs-test-namespace'],
privileges: ['read', 'write'],
privileges: ['read', 'monitor'],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
*/
import type { SavedObjectsClientContract } from 'kibana/server';

import type { FullAgentPolicyOutputPermissions, RegistryDataStreamPermissions } from '../../common';
import type { FullAgentPolicyOutputPermissions, RegistryDataStreamPrivileges } from '../../common';
import { PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES } from '../constants';
import { getPackageInfo } from '../../server/services/epm/packages';

import type { PackagePolicy } from '../types';

export const DEFAULT_PERMISSIONS = {
Expand All @@ -22,7 +22,7 @@ export const DEFAULT_PERMISSIONS = {
'synthetics-*',
'.logs-endpoint.diagnostic.collection-*',
],
privileges: ['auto_configure', 'create_doc'],
privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
},
],
};
Expand Down Expand Up @@ -104,12 +104,16 @@ export async function storedPackagePoliciesToAgentPermissions(
return;
}

const ds = {
const ds: DataStreamMeta = {
type: stream.data_stream.type,
dataset:
stream.compiled_stream?.data_stream?.dataset ?? stream.data_stream.dataset,
};

if (stream.data_stream.elasticsearch) {
ds.elasticsearch = stream.data_stream.elasticsearch;
}

dataStreams_.push(ds);
});

Expand All @@ -121,7 +125,7 @@ export async function storedPackagePoliciesToAgentPermissions(
packagePolicy.name,
{
indices: dataStreamsForPermissions.map((ds) =>
getDataStreamPermissions(ds, packagePolicy.namespace)
getDataStreamPrivileges(ds, packagePolicy.namespace)
),
},
];
Expand All @@ -136,10 +140,12 @@ interface DataStreamMeta {
dataset: string;
dataset_is_prefix?: boolean;
hidden?: boolean;
permissions?: RegistryDataStreamPermissions;
elasticsearch?: {
privileges?: RegistryDataStreamPrivileges;
};
}

export function getDataStreamPermissions(dataStream: DataStreamMeta, namespace: string = '*') {
export function getDataStreamPrivileges(dataStream: DataStreamMeta, namespace: string = '*') {
let index = `${dataStream.type}-${dataStream.dataset}`;

if (dataStream.dataset_is_prefix) {
Expand All @@ -152,8 +158,12 @@ export function getDataStreamPermissions(dataStream: DataStreamMeta, namespace:

index += `-${namespace}`;

const privileges = dataStream?.elasticsearch?.privileges?.indices?.length
? dataStream.elasticsearch.privileges.indices
: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES;

return {
names: [index],
privileges: dataStream.permissions?.indices || ['auto_configure', 'create_doc'],
privileges,
};
}
130 changes: 126 additions & 4 deletions x-pack/plugins/fleet/server/services/package_policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,25 @@ import {
import type { SavedObjectsClient, SavedObjectsUpdateResponse } from 'src/core/server';
import type { KibanaRequest } from 'kibana/server';

import type { PackageInfo, PackagePolicySOAttributes, AgentPolicySOAttributes } from '../types';
import type {
PackageInfo,
PackagePolicySOAttributes,
AgentPolicySOAttributes,
PostPackagePolicyDeleteCallback,
RegistryDataStream,
PackagePolicyInputStream,
} from '../types';
import { createPackagePolicyMock } from '../../common/mocks';

import type { PutPackagePolicyUpdateCallback, PostPackagePolicyCreateCallback } from '..';

import { createAppContextStartContractMock, xpackMocks } from '../mocks';

import type { PostPackagePolicyDeleteCallback } from '../types';

import type { DeletePackagePoliciesResponse } from '../../common';

import { IngestManagerError } from '../errors';

import { packagePolicyService } from './package_policy';
import { packagePolicyService, _applyIndexPrivileges } from './package_policy';
import { appContextService } from './app_context';

async function mockedGetAssetsData(_a: any, _b: any, dataset: string) {
Expand Down Expand Up @@ -1069,3 +1075,119 @@ describe('Package policy service', () => {
});
});
});

describe('_applyIndexPrivileges()', () => {
function createPackageStream(indexPrivileges?: string[]): RegistryDataStream {
const stream: RegistryDataStream = {
type: '',
dataset: '',
title: '',
release: '',
package: '',
path: '',
};

if (indexPrivileges) {
stream.elasticsearch = {
privileges: {
indices: indexPrivileges,
},
};
}

return stream;
}

function createInputStream(
opts: Partial<PackagePolicyInputStream> = {}
): PackagePolicyInputStream {
return {
id: '',
enabled: true,
data_stream: {
dataset: '',
type: '',
},
...opts,
};
}

beforeAll(async () => {
appContextService.start(createAppContextStartContractMock());
});

it('should do nothing if packageStream has no privileges', () => {
const packageStream = createPackageStream();
const inputStream = createInputStream();

const streamOut = _applyIndexPrivileges(packageStream, inputStream);
expect(streamOut).toEqual(inputStream);
});

it('should not apply privileges if all privileges are forbidden', () => {
const forbiddenPrivileges = ['write', 'delete', 'delete_index', 'all'];
const packageStream = createPackageStream(forbiddenPrivileges);
const inputStream = createInputStream();

const streamOut = _applyIndexPrivileges(packageStream, inputStream);
expect(streamOut).toEqual(inputStream);
});

it('should not apply privileges if all privileges are unrecognized', () => {
const unrecognizedPrivileges = ['idnotexist', 'invalidperm'];
const packageStream = createPackageStream(unrecognizedPrivileges);
const inputStream = createInputStream();

const streamOut = _applyIndexPrivileges(packageStream, inputStream);
expect(streamOut).toEqual(inputStream);
});

it('should apply privileges if all privileges are valid', () => {
const validPrivileges = [
'auto_configure',
'create_doc',
'maintenance',
'monitor',
'read',
'read_cross_cluster',
];

const packageStream = createPackageStream(validPrivileges);
const inputStream = createInputStream();
const expectedStream = {
...inputStream,
data_stream: {
...inputStream.data_stream,
elasticsearch: {
privileges: {
indices: validPrivileges,
},
},
},
};

const streamOut = _applyIndexPrivileges(packageStream, inputStream);
expect(streamOut).toEqual(expectedStream);
});

it('should only apply valid privileges when there is a mix of valid and invalid', () => {
const mixedPrivileges = ['auto_configure', 'read_cross_cluster', 'idontexist', 'delete'];

const packageStream = createPackageStream(mixedPrivileges);
const inputStream = createInputStream();
const expectedStream = {
...inputStream,
data_stream: {
...inputStream.data_stream,
elasticsearch: {
privileges: {
indices: ['auto_configure', 'read_cross_cluster'],
},
},
},
};

const streamOut = _applyIndexPrivileges(packageStream, inputStream);
expect(streamOut).toEqual(expectedStream);
});
});
Loading

0 comments on commit 75c6dfa

Please sign in to comment.