-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fleet] RBAC - Make agents write APIs space aware (#188507)
## Summary Relates to #185040 This PR makes the following Fleet agents API space aware: * `PUT /agents/{agentId}` * `DELETE /agents/{agentId}` * `POST /agents/bulk_update_agent_tags` Actions created from `POST /agents/bulk_update_agent_tags` have the `namespaces` property populated with the current space. I am opening this PR with a few endpoints to get early feedback and make this more agile. Other endpoints will be implemented in a followup PR. ### Testing 1. Enroll an agent in the default space. 2. Create a custom space and enroll an agent in it. 3. From the default space, test the `PUT /agents/{agentId}` and `DELETE /agents/{agentId}` endpoints and check that the request fails for the agent in the custom space. 4. Same test from the custom space. 5. From the default space, test the `POST /agents/bulk_update_agent_tags` with all agents ids and check that only the agents in the default space get updated. 6. Same test from the custom space. 7. Review the actions created from the bulk tag updates (the easiest way is `GET .fleet-actions/_search`) and ensure the `namespaces` property is correct. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
b7e66eb
commit 9e71c7e
Showing
10 changed files
with
458 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
119 changes: 119 additions & 0 deletions
119
x-pack/plugins/fleet/server/services/agents/namespace.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* 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 { appContextService } from '../app_context'; | ||
|
||
import type { Agent } from '../../types'; | ||
|
||
import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace'; | ||
|
||
jest.mock('../app_context'); | ||
|
||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>; | ||
|
||
describe('isAgentInNamespace', () => { | ||
describe('with the useSpaceAwareness feature flag disabled', () => { | ||
beforeEach(() => { | ||
mockedAppContextService.getExperimentalFeatures.mockReturnValue({ | ||
useSpaceAwareness: false, | ||
} as any); | ||
}); | ||
|
||
it('returns true even if the agent is in a different space', () => { | ||
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; | ||
expect(isAgentInNamespace(agent, 'space2')).toEqual(true); | ||
}); | ||
}); | ||
|
||
describe('with the useSpaceAwareness feature flag enabled', () => { | ||
beforeEach(() => { | ||
mockedAppContextService.getExperimentalFeatures.mockReturnValue({ | ||
useSpaceAwareness: true, | ||
} as any); | ||
}); | ||
|
||
describe('when the namespace is defined', () => { | ||
it('returns true if the agent namespaces include the namespace', () => { | ||
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; | ||
expect(isAgentInNamespace(agent, 'space1')).toEqual(true); | ||
}); | ||
|
||
it('returns false if the agent namespaces do not include the namespace', () => { | ||
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; | ||
expect(isAgentInNamespace(agent, 'space2')).toEqual(false); | ||
}); | ||
|
||
it('returns false if the agent has zero length namespaces', () => { | ||
const agent = { id: '123', namespaces: [] as string[] } as Agent; | ||
expect(isAgentInNamespace(agent, 'space1')).toEqual(false); | ||
}); | ||
|
||
it('returns false if the agent does not have namespaces', () => { | ||
const agent = { id: '123' } as Agent; | ||
expect(isAgentInNamespace(agent, 'space1')).toEqual(false); | ||
}); | ||
}); | ||
|
||
describe('when the namespace is undefined', () => { | ||
it('returns true if the agent does not have namespaces', () => { | ||
const agent = { id: '123' } as Agent; | ||
expect(isAgentInNamespace(agent)).toEqual(true); | ||
}); | ||
|
||
it('returns true if the agent has zero length namespaces', () => { | ||
const agent = { id: '123', namespaces: [] as string[] } as Agent; | ||
expect(isAgentInNamespace(agent)).toEqual(true); | ||
}); | ||
|
||
it('returns true if the agent namespaces include the default one', () => { | ||
const agent = { id: '123', namespaces: ['default'] } as Agent; | ||
expect(isAgentInNamespace(agent)).toEqual(true); | ||
}); | ||
|
||
it('returns false if the agent namespaces include the default one', () => { | ||
const agent = { id: '123', namespaces: ['space1'] } as Agent; | ||
expect(isAgentInNamespace(agent)).toEqual(false); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('agentsKueryNamespaceFilter', () => { | ||
describe('with the useSpaceAwareness feature flag disabled', () => { | ||
beforeEach(() => { | ||
mockedAppContextService.getExperimentalFeatures.mockReturnValue({ | ||
useSpaceAwareness: false, | ||
} as any); | ||
}); | ||
|
||
it('returns undefined if the useSpaceAwareness feature flag disabled', () => { | ||
expect(agentsKueryNamespaceFilter('space1')).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('with the useSpaceAwareness feature flag enabled', () => { | ||
beforeEach(() => { | ||
mockedAppContextService.getExperimentalFeatures.mockReturnValue({ | ||
useSpaceAwareness: true, | ||
} as any); | ||
}); | ||
|
||
it('returns undefined if the namespace is undefined', () => { | ||
expect(agentsKueryNamespaceFilter()).toBeUndefined(); | ||
}); | ||
|
||
it('returns a kuery for the default space', () => { | ||
expect(agentsKueryNamespaceFilter('default')).toEqual( | ||
'namespaces:(default) or not namespaces:*' | ||
); | ||
}); | ||
|
||
it('returns a kuery for custom spaces', () => { | ||
expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* 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 { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; | ||
|
||
import { appContextService } from '../app_context'; | ||
|
||
import type { Agent } from '../../types'; | ||
|
||
export function isAgentInNamespace(agent: Agent, namespace?: string) { | ||
const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; | ||
if (!useSpaceAwareness) { | ||
return true; | ||
} | ||
|
||
return ( | ||
(namespace && agent.namespaces?.includes(namespace)) || | ||
(!namespace && | ||
(!agent.namespaces || | ||
agent.namespaces.length === 0 || | ||
agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING))) | ||
); | ||
} | ||
|
||
export function agentsKueryNamespaceFilter(namespace?: string) { | ||
const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; | ||
if (!useSpaceAwareness || !namespace) { | ||
return; | ||
} | ||
return namespace === DEFAULT_NAMESPACE_STRING | ||
? `namespaces:(${DEFAULT_NAMESPACE_STRING}) or not namespaces:*` | ||
: `namespaces:(${namespace})`; | ||
} |
Oops, something went wrong.