|
| 1 | +/* |
| 2 | + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
| 3 | + * or more contributor license agreements. Licensed under the Elastic License |
| 4 | + * 2.0; you may not use this file except in compliance with the Elastic License |
| 5 | + * 2.0. |
| 6 | + */ |
| 7 | + |
| 8 | +import { sortBy } from 'lodash'; |
| 9 | + |
| 10 | +import type { Root } from '@kbn/core-root-server-internal'; |
| 11 | +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; |
| 12 | +import { |
| 13 | + createRootWithCorePlugins, |
| 14 | + createTestServers, |
| 15 | + type TestElasticsearchUtils, |
| 16 | +} from '@kbn/core-test-helpers-kbn-server'; |
| 17 | +import { extractMigrationInfo } from '@kbn/core-test-helpers-so-type-serializer'; |
| 18 | + |
| 19 | +import type { EncryptedSavedObjectsService } from '../../server/crypto'; |
| 20 | +import * as EncryptedSavedObjectsModule from '../../server/saved_objects'; |
| 21 | + |
| 22 | +// This will only change if new ESOs are introduced. This number should never get smaller. |
| 23 | +export const ESO_TYPES_COUNT = 18 as const; |
| 24 | + |
| 25 | +describe('checking changes on all registered encrypted SO types', () => { |
| 26 | + let esServer: TestElasticsearchUtils; |
| 27 | + let root: Root; |
| 28 | + let typeRegistry: ISavedObjectTypeRegistry; |
| 29 | + let esoService: EncryptedSavedObjectsService; |
| 30 | + |
| 31 | + beforeAll(async () => { |
| 32 | + // the ESO service is passed to the setupSavedObjects function, so we can obtain it by spying on this call. |
| 33 | + // Normally this service is not accessible outside of the ESO plugin. |
| 34 | + const setupSavedObjectsSpy = jest.spyOn(EncryptedSavedObjectsModule, 'setupSavedObjects'); |
| 35 | + |
| 36 | + const { startES } = createTestServers({ |
| 37 | + adjustTimeout: (t: number) => jest.setTimeout(t), |
| 38 | + }); |
| 39 | + |
| 40 | + esServer = await startES(); |
| 41 | + root = createRootWithCorePlugins({}, { oss: false }); |
| 42 | + await root.preboot(); |
| 43 | + await root.setup(); |
| 44 | + const coreStart = await root.start(); |
| 45 | + typeRegistry = coreStart.savedObjects.getTypeRegistry(); |
| 46 | + esoService = setupSavedObjectsSpy.mock.calls[0][0].service as EncryptedSavedObjectsService; |
| 47 | + }); |
| 48 | + |
| 49 | + afterAll(async () => { |
| 50 | + if (root) { |
| 51 | + await root.shutdown(); |
| 52 | + } |
| 53 | + if (esServer) { |
| 54 | + await esServer.stop(); |
| 55 | + } |
| 56 | + }); |
| 57 | + |
| 58 | + // This test is meant to fail when any change is made to ESO registrations (change or addition). |
| 59 | + // Just update the snapshot by running this test file via jest_integration with `-u` and push the update. |
| 60 | + // The intent is to trigger a code review from the Kibana Security team to review the ESO changes. |
| 61 | + // The number of types in the hashMap should never be reduced, it should only increase. |
| 62 | + it('detecting changes to encryption registration definitions', () => { |
| 63 | + const hashMap = esoService.getRegisteredTypeHashMap(); |
| 64 | + |
| 65 | + expect(hashMap).toMatchInlineSnapshot(` |
| 66 | + Object { |
| 67 | + "action": "4e9f7946dfcbee267e685618638f76f3d55e65c949bd259487dc4bf004018478", |
| 68 | + "action_task_params": "06aa563283bdcd5c07ec433a7d0b8425019ad11d75595ee1431691667ecd2cec", |
| 69 | + "ad_hoc_run_params": "6539367aa4ae8340c62f123c3457c6b8d7873c92de68651c70d41028dfe7ed32", |
| 70 | + "alert": "d961ff113e2b7995a49483b8937fcbdccfe425ac82b59a050931cd620b043ed1", |
| 71 | + "api_key_pending_invalidation": "ce3641d95c31bcc2880a294f0123060dcc5026f0a493befdda74924a7ea5c4a0", |
| 72 | + "connector_token": "16ca2154c13c5ee3d3a45b55d4ea6cd33aeaceaef3dc229b002d25470bfc9b3b", |
| 73 | + "entity-discovery-api-key": "cd3b5230a513d2d3503583223e48362fbbbc7812aa4710579a62acfa5bbc30e6", |
| 74 | + "fleet-fleet-server-host": "3b8d0809aaf8a133596307bc29328207c7ceee1dc72233da75141ec47ad8d327", |
| 75 | + "fleet-message-signing-keys": "5cdcf6bf85247267f8876bda4226e871dbfefe01f050e898db7cbc267d57a275", |
| 76 | + "fleet-uninstall-tokens": "6e7d75921dcce46e566f175eab1b0e3825fe565f20cdb3c984e7037934d61e23", |
| 77 | + "ingest-download-sources": "23eb3cf789fe13b4899215c6f919705b8a44b89f8feba7181e1f5db3c7699d40", |
| 78 | + "ingest-outputs": "d66716d5333484a25c57f7917bead5ac2576ec57a4b9eb61701b573f35ab62ad", |
| 79 | + "privmon-api-key": "7d7b76b3bc5287a784518731ba66d4f761052177fc04b1a85e5605846ab9de42", |
| 80 | + "synthetics-monitor": "f1c060b7be3b30187c4adcb35d74f1fa8a4290bd7faf04fec869de2aa387e21b", |
| 81 | + "synthetics-monitor-multi-space": "39c4c6abd28c4173f77c1c89306e92b6b92492c0029274e10620a170be4d4a67", |
| 82 | + "synthetics-param": "747ba9d1b7addf5b131713abe7868bd767af6ce0cf8b6b0f335f4ef34b280c7e", |
| 83 | + "task": "2d8e9bf532f469805b82051f545b915785d99eabfa050cb1aefbc715c6096b97", |
| 84 | + "uptime-synthetics-api-key": "5ca81f180763e85397fa8c6508adcd60efd0f916e29bac6dcd5b4564f1db7375", |
| 85 | + } |
| 86 | + `); |
| 87 | + expect(Object.keys(hashMap).length).toEqual(ESO_TYPES_COUNT); |
| 88 | + }); |
| 89 | + |
| 90 | + // This test is meant to fail and require an update when a new model version is introduced to any ESO types. |
| 91 | + // Just update the snapshot by running this test file via jest_integration with `-u` and push the update. |
| 92 | + // There are tests in core which will catch when an SO type is changed, and help the Core team enforce when |
| 93 | + // a model version needs to be added. This purpose of this test is to ensure that new model versions for ESOs |
| 94 | + // are implemented to the security team's guidelines to adhere to all zero-downtime upgrade considerations. |
| 95 | + it('detecting new model versions in registered encrypted types', () => { |
| 96 | + const esoTypes = esoService.getRegisteredTypes(); |
| 97 | + const soTypestoCheck = typeRegistry |
| 98 | + .getAllTypes() |
| 99 | + .filter((soType) => esoTypes.includes(soType.name)); |
| 100 | + |
| 101 | + const modelVersionMap: string[] = sortBy(soTypestoCheck, 'name').flatMap((type, index) => { |
| 102 | + const typeMigrationInfo = sortBy(extractMigrationInfo(type).modelVersions, 'version') |
| 103 | + .reverse() |
| 104 | + .map((mv) => `${type.name}|${mv.version}`); |
| 105 | + |
| 106 | + return typeMigrationInfo; |
| 107 | + }); |
| 108 | + |
| 109 | + expect(modelVersionMap).toMatchInlineSnapshot(` |
| 110 | + Array [ |
| 111 | + "action|1", |
| 112 | + "action_task_params|2", |
| 113 | + "action_task_params|1", |
| 114 | + "ad_hoc_run_params|2", |
| 115 | + "ad_hoc_run_params|1", |
| 116 | + "alert|6", |
| 117 | + "alert|5", |
| 118 | + "alert|4", |
| 119 | + "alert|3", |
| 120 | + "alert|2", |
| 121 | + "alert|1", |
| 122 | + "api_key_pending_invalidation|1", |
| 123 | + "connector_token|1", |
| 124 | + "fleet-fleet-server-host|2", |
| 125 | + "fleet-fleet-server-host|1", |
| 126 | + "fleet-uninstall-tokens|1", |
| 127 | + "ingest-download-sources|1", |
| 128 | + "ingest-outputs|8", |
| 129 | + "ingest-outputs|7", |
| 130 | + "ingest-outputs|6", |
| 131 | + "ingest-outputs|5", |
| 132 | + "ingest-outputs|4", |
| 133 | + "ingest-outputs|3", |
| 134 | + "ingest-outputs|2", |
| 135 | + "ingest-outputs|1", |
| 136 | + "synthetics-monitor|2", |
| 137 | + "synthetics-monitor|1", |
| 138 | + "task|6", |
| 139 | + "task|5", |
| 140 | + "task|4", |
| 141 | + "task|3", |
| 142 | + "task|2", |
| 143 | + "task|1", |
| 144 | + ] |
| 145 | + `); |
| 146 | + }); |
| 147 | +}); |
0 commit comments