-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Fleet] Uninstalltoken saved object namespace agnostic and space aware #190741
Changes from all commits
a8fd6f3
16c54e3
6e36819
87e52c6
d4e5963
6aa8e8b
bc5bb99
7c536ef
51677c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,11 @@ import { createHash } from 'crypto'; | |
|
||
import type { KibanaRequest } from '@kbn/core-http-server'; | ||
|
||
import type { SavedObjectsClientContract } from '@kbn/core/server'; | ||
import { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add some more tests here? It would be great to cover some of the functionalities added below. It will give us more confidence, for instance in the event of the feature flag clean up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just added some in 6aa8e8b |
||
SECURITY_EXTENSION_ID, | ||
SPACES_EXTENSION_ID, | ||
type SavedObjectsClientContract, | ||
} from '@kbn/core/server'; | ||
import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; | ||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; | ||
|
||
|
@@ -29,6 +33,7 @@ import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE } from '../../../constants'; | |
import { createAppContextStartContractMock, type MockedFleetAppContext } from '../../../mocks'; | ||
import { appContextService } from '../../app_context'; | ||
import { agentPolicyService } from '../../agent_policy'; | ||
import { isSpaceAwarenessEnabled } from '../../spaces/helpers'; | ||
|
||
import { UninstallTokenService, type UninstallTokenServiceInterface } from '.'; | ||
|
||
|
@@ -42,6 +47,8 @@ interface TokenSO { | |
created_at: string; | ||
} | ||
|
||
jest.mock('../../spaces/helpers'); | ||
|
||
describe('UninstallTokenService', () => { | ||
const now = new Date().toISOString(); | ||
const aDayAgo = new Date(Date.now() - 24 * 3600 * 1000).toISOString(); | ||
|
@@ -194,7 +201,7 @@ describe('UninstallTokenService', () => { | |
); | ||
} | ||
|
||
function setupMocks(canEncrypt: boolean = true) { | ||
function setupMocks(canEncrypt: boolean = true, scoppedInSpace?: string) { | ||
mockContext = createAppContextStartContractMock(); | ||
mockContext.encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup({ | ||
canEncrypt, | ||
|
@@ -204,13 +211,22 @@ describe('UninstallTokenService', () => { | |
mockContext.encryptedSavedObjectsStart!.getClient() as jest.Mocked<EncryptedSavedObjectsClient>; | ||
soClientMock = appContextService | ||
.getSavedObjects() | ||
.getScopedClient({} as unknown as KibanaRequest) as jest.Mocked<SavedObjectsClientContract>; | ||
.getScopedClient({} as unknown as KibanaRequest, { | ||
excludedExtensions: [SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID], | ||
}) as jest.Mocked<SavedObjectsClientContract>; | ||
agentPolicyService.deployPolicies = jest.fn(); | ||
|
||
getAgentPoliciesByIDsMock = jest.fn().mockResolvedValue([]); | ||
agentPolicyService.getByIDs = getAgentPoliciesByIDsMock; | ||
|
||
uninstallTokenService = new UninstallTokenService(esoClientMock); | ||
if (scoppedInSpace) { | ||
soClientMock.getCurrentNamespace.mockReturnValue(scoppedInSpace); | ||
} | ||
|
||
uninstallTokenService = new UninstallTokenService( | ||
esoClientMock, | ||
scoppedInSpace ? soClientMock : undefined | ||
); | ||
mockFind(canEncrypt); | ||
mockCreatePointInTimeFinder(canEncrypt); | ||
mockCreatePointInTimeFinderAsInternalUser(); | ||
|
@@ -240,6 +256,7 @@ describe('UninstallTokenService', () => { | |
|
||
beforeEach(() => { | ||
setupMocks(canEncrypt); | ||
jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); | ||
}); | ||
|
||
describe('get uninstall tokens', () => { | ||
|
@@ -277,6 +294,78 @@ describe('UninstallTokenService', () => { | |
); | ||
}); | ||
|
||
it('filter namespace with scopped service and space awareneness enabled', async () => { | ||
jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); | ||
setupMocks(canEncrypt, 'test'); | ||
|
||
const so = getDefaultSO(canEncrypt); | ||
mockCreatePointInTimeFinderAsInternalUser([so]); | ||
getAgentPoliciesByIDsMock.mockResolvedValue([ | ||
{ id: so.attributes.policy_id, name: 'cheese' }, | ||
] as Array<Partial<AgentPolicy>>); | ||
|
||
const token = await uninstallTokenService.getToken(so.id); | ||
|
||
const expectedItem: UninstallToken = { | ||
id: so.id, | ||
policy_id: so.attributes.policy_id, | ||
policy_name: 'cheese', | ||
token: getToken(so, canEncrypt), | ||
created_at: so.created_at, | ||
}; | ||
|
||
expect(token).toEqual(expectedItem); | ||
|
||
expect(esoClientMock.createPointInTimeFinderDecryptedAsInternalUser).toHaveBeenCalledWith( | ||
{ | ||
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, | ||
filter: `(${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.namespaces:test) and (${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:${so.id}")`, | ||
perPage: SO_SEARCH_LIMIT, | ||
} | ||
); | ||
expect(getAgentPoliciesByIDsMock).toHaveBeenCalledWith( | ||
soClientMock, | ||
[so.attributes.policy_id], | ||
{ ignoreMissing: true } | ||
); | ||
}); | ||
|
||
it('do not filter namespace with scopped service and space awareneness disabled', async () => { | ||
jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); | ||
setupMocks(canEncrypt, 'test'); | ||
|
||
const so = getDefaultSO(canEncrypt); | ||
mockCreatePointInTimeFinderAsInternalUser([so]); | ||
getAgentPoliciesByIDsMock.mockResolvedValue([ | ||
{ id: so.attributes.policy_id, name: 'cheese' }, | ||
] as Array<Partial<AgentPolicy>>); | ||
|
||
const token = await uninstallTokenService.getToken(so.id); | ||
|
||
const expectedItem: UninstallToken = { | ||
id: so.id, | ||
policy_id: so.attributes.policy_id, | ||
policy_name: 'cheese', | ||
token: getToken(so, canEncrypt), | ||
created_at: so.created_at, | ||
}; | ||
|
||
expect(token).toEqual(expectedItem); | ||
|
||
expect(esoClientMock.createPointInTimeFinderDecryptedAsInternalUser).toHaveBeenCalledWith( | ||
{ | ||
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, | ||
filter: `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:${so.id}"`, | ||
perPage: SO_SEARCH_LIMIT, | ||
} | ||
); | ||
expect(getAgentPoliciesByIDsMock).toHaveBeenCalledWith( | ||
soClientMock, | ||
[so.attributes.policy_id], | ||
{ ignoreMissing: true } | ||
); | ||
}); | ||
|
||
it('sets `policy_name` to `null` if linked policy does not exist', async () => { | ||
const so = getDefaultSO(canEncrypt); | ||
mockCreatePointInTimeFinderAsInternalUser([so]); | ||
|
@@ -341,6 +430,72 @@ describe('UninstallTokenService', () => { | |
expect(actualItems).toEqual(expectedItems); | ||
}); | ||
|
||
it('filter by namespace if service is scopped and space awareness is enabled', async () => { | ||
setupMocks(canEncrypt, 'test'); | ||
jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(true); | ||
const so = getDefaultSO(canEncrypt); | ||
const so2 = getDefaultSO2(canEncrypt); | ||
getAgentPoliciesByIDsMock.mockResolvedValue([ | ||
{ id: so2.attributes.policy_id, name: 'only I have a name' }, | ||
] as Array<Partial<AgentPolicy>>); | ||
|
||
const actualItems = (await uninstallTokenService.getTokenMetadata()).items; | ||
const expectedItems: UninstallTokenMetadata[] = [ | ||
{ | ||
id: so.id, | ||
policy_id: so.attributes.policy_id, | ||
policy_name: null, | ||
created_at: so.created_at, | ||
}, | ||
{ | ||
id: so2.id, | ||
policy_id: so2.attributes.policy_id, | ||
policy_name: 'only I have a name', | ||
created_at: so2.created_at, | ||
}, | ||
]; | ||
expect(actualItems).toEqual(expectedItems); | ||
|
||
expect(soClientMock.createPointInTimeFinder).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
filter: `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.namespaces:test`, | ||
}) | ||
); | ||
}); | ||
|
||
it('do not filter by namespace if service is scopped and space awareness is disabled', async () => { | ||
setupMocks(canEncrypt, 'test'); | ||
jest.mocked(isSpaceAwarenessEnabled).mockResolvedValue(false); | ||
const so = getDefaultSO(canEncrypt); | ||
const so2 = getDefaultSO2(canEncrypt); | ||
getAgentPoliciesByIDsMock.mockResolvedValue([ | ||
{ id: so2.attributes.policy_id, name: 'only I have a name' }, | ||
] as Array<Partial<AgentPolicy>>); | ||
|
||
const actualItems = (await uninstallTokenService.getTokenMetadata()).items; | ||
const expectedItems: UninstallTokenMetadata[] = [ | ||
{ | ||
id: so.id, | ||
policy_id: so.attributes.policy_id, | ||
policy_name: null, | ||
created_at: so.created_at, | ||
}, | ||
{ | ||
id: so2.id, | ||
policy_id: so2.attributes.policy_id, | ||
policy_name: 'only I have a name', | ||
created_at: so2.created_at, | ||
}, | ||
]; | ||
expect(actualItems).toEqual(expectedItems); | ||
|
||
expect(soClientMock.createPointInTimeFinder).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
filter: undefined, | ||
}) | ||
); | ||
}); | ||
|
||
it('should throw error if created_at is missing', async () => { | ||
const defaultBuckets = getDefaultBuckets(canEncrypt); | ||
defaultBuckets[0].latest.hits.hits[0]._source.created_at = ''; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the
useSpaceAwareness
experimental feature potentially being used anywhere of consequence (by customers, internal deployments, etc)?Single space ESOs use the object's namespace when constructing AAD. Any instances of Kibana where
useSpaceAwareness
is active, that then upgrade to a version with this change, would cause existing message signing keys and uninstall token objects to be undecryptable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No
useSpaceAwareness
is not used in any long standing deployment, the feature is still in development, we used that feature flag more as an integration branch for now than a real feature flag