import type { Crypto , EncryptOutput } from '@elastic/node-crypto' ; import stringify from 'json-stable-stringify' ; import typeDetect from 'type-detect' ; import type { Logger } from 'src/core/server' ; import type { AuthenticatedUser } from '../../../security/common/model' ; import { EncryptedSavedObjectAttributesDefinition } from './encrypted_saved_object_type_definition' ; import { EncryptionError , EncryptionErrorOperation } from './encryption_error' ; /**
 * Describes the attributes to encrypt. By default, attribute values won't be exposed to end-users
 * and can only be consumed by the internal Kibana server. If end-users should have access to the
 * encrypted values use `dangerouslyExposeValue: true`
 */ export interface AttributeToEncrypt { readonly key : string ; readonly dangerouslyExposeValue ? : boolean ; } /**
 * Describes the registration entry for the saved object type that contain attributes that need to
 * be encrypted.
 */ export interface EncryptedSavedObjectTypeRegistration { readonly type : string ; readonly attributesToEncrypt : ReadonlySet < string | AttributeToEncrypt > ; readonly attributesToExcludeFromAAD ? : ReadonlySet < string > ; } /**
 * Uniquely identifies saved object.
 */ export interface SavedObjectDescriptor { readonly id : string ; readonly type : string ; readonly namespace ? : string ; } /**
 * Describes parameters that are common for all EncryptedSavedObjectsService public methods.
 */ interface CommonParameters { /**
     * User on behalf of the method is called if determined.
     */ user ? : AuthenticatedUser ; } /**
 * Describes parameters for the decrypt methods.
 */ interface DecryptParameters extends CommonParameters { /**
     * Indicates whether decryption should only be performed using secondary decryption-only keys.
     */ omitPrimaryEncryptionKey ? : boolean ; /**
     * Indicates whether the object to be decrypted is being converted from a single-namespace type to a multi-namespace type. In this case,
     * we may need to attempt decryption twice: once with a namespace in the descriptor (for use during index migration), and again without a
     * namespace in the descriptor (for use during object migration). In other words, if the object is being decrypted during index migration,
     * the object was previously encrypted with its namespace in the descriptor portion of the AAD; on the other hand, if the object is being
     * decrypted during object migration, the object was never encrypted with its namespace in the descriptor portion of the AAD.
     */ isTypeBeingConverted ? : boolean ; /**
     * If the originId (old object ID) is present and the object is being converted from a single-namespace type to a multi-namespace type,
     * we will attempt to decrypt with both the old object ID and the current object ID.
     */ originId ? : string ; } interface EncryptedSavedObjectsServiceOptions { /**
     * Service logger instance.
     */ logger : Logger ; /**
     * NodeCrypto instance used for both encryption and decryption.
     */ primaryCrypto ? : Crypto ; /**
     * NodeCrypto instances used ONLY for decryption (i.e. rotated encryption keys).
     */ decryptionOnlyCryptos ? : Readonly < Crypto [ ] > ; } param descriptor export function descriptorToArray ( descriptor ) { return descriptor . namespace ? [ descriptor . namespace , descriptor . type , descriptor . id ] : [ descriptor . type , descriptor . id ] ; } /**
 * Represents the service that tracks all saved object types that might contain attributes that need
 * to be encrypted before they are stored and eventually decrypted when retrieved. The service
 * performs encryption only based on registered saved object types that are known to contain such
 * attributes.
 */ export class EncryptedSavedObjectsService { /**
     * Map of all registered saved object types where the `key` is saved object type and the `value`
     * is the definition (names of attributes that need to be encrypted etc.).
     */ private readonly typeDefinitions : Map < string , EncryptedSavedObjectAttributesDefinition > = new Map ( ) ; constructor ( private readonly options ) { } param typeRegistration throws throws throws public registerType ( typeRegistration ) { if ( typeRegistration . attributesToEncrypt . size === 0 ) { throw new Error ( `The "attributesToEncrypt" array for "${ typeRegistration . type }" is empty.` ) ; } if ( this . typeDefinitions . has ( typeRegistration . type ) ) { throw new Error ( `The "${ typeRegistration . type }" saved object type is already registered.` ) ; } this . typeDefinitions . set ( typeRegistration . type , new EncryptedSavedObjectAttributesDefinition ( typeRegistration ) ) ; } param type public isRegistered ( type ) { return this . typeDefinitions . has ( type ) ; } param descriptor param attributes param originalAttributes param params public async stripOrDecryptAttributes < T extends Record < string , unknown > > ( descriptor , attributesToStripOrDecrypt , originalAttributes ? , params ? ) { const { attributes , attributesToDecrypt } = this . prepareAttributesForStripOrDecrypt ( descriptor , attributesToStripOrDecrypt , originalAttributes ) ; try { const decryptedAttributes = attributesToDecrypt ? await this . decryptAttributes ( descriptor , attributesToDecrypt , params ) : { } ; return { attributes : { ... attributes , ... decryptedAttributes } } ; } catch ( error ) { return { attributes , error } ; } } param descriptor param attributesToStripOrDecrypt param originalAttributes param params public stripOrDecryptAttributesSync < T extends Record < string , unknown > > ( descriptor , attributesToStripOrDecrypt , originalAttributes ? , params ? ) { const { attributes , attributesToDecrypt } = this . prepareAttributesForStripOrDecrypt ( descriptor , attributesToStripOrDecrypt , originalAttributes ) ; try { const decryptedAttributes = attributesToDecrypt ? this . decryptAttributesSync ( descriptor , attributesToDecrypt , params ) : { } ; return { attributes : { ... attributes , ... decryptedAttributes } } ; } catch ( error ) { return { attributes , error } ; } } private private prepareAttributesForStripOrDecrypt < T extends Record < string , unknown > > ( descriptor , attributes , originalAttributes ? ) { const typeDefinition = this . typeDefinitions . get ( descriptor . type ) ; if ( typeDefinition === undefined ) { return { attributes , attributesToDecrypt : null } ; } let attributesToDecrypt ; const clonedAttributes = { } ; for ( const [ attributeName , attributeValue ] of Object . entries ( attributes ) ) { if ( typeDefinition . shouldBeStripped ( attributeName ) ) { continue ; } if ( ! typeDefinition . shouldBeEncrypted ( attributeName ) ) { clonedAttributes [ attributeName ] = attributeValue ; } else if ( originalAttributes ) { clonedAttributes [ attributeName ] = originalAttributes [ attributeName ] ; } else if ( ! attributesToDecrypt ) { attributesToDecrypt = Object . fromEntries ( Object . entries ( attributes ) . filter ( ( [ key ] ) => ! typeDefinition . shouldBeStripped ( key ) ) ) as T ; } } return { attributes : clonedAttributes as T , attributesToDecrypt : attributesToDecrypt && Object . keys ( attributesToDecrypt ) . length > 0 ? attributesToDecrypt : null , } ; } private * attributesToEncryptIterator < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const typeDefinition = this . typeDefinitions . get ( descriptor . type ) ; if ( typeDefinition === undefined ) { return attributes ; } let encryptionAAD ; const encryptedAttributes = { } ; for ( const attributeName of typeDefinition . attributesToEncrypt ) { const attributeValue = attributes [ attributeName ] ; if ( attributeValue != null ) { if ( ! encryptionAAD ) { encryptionAAD = this . getAAD ( typeDefinition , descriptor , attributes ) ; } try { encryptedAttributes [ attributeName ] = ( yield [ attributeValue , encryptionAAD ] ) ! ; } catch ( err ) { this . options . logger . error ( `Failed to encrypt "${ attributeName }" attribute: ${ err . message || err }` ) ; throw new EncryptionError ( `Unable to encrypt attribute "${ attributeName }"` , attributeName , EncryptionErrorOperation . Encryption , err ) ; } } } const encryptedAttributesKeys = Object . keys ( encryptedAttributes ) ; if ( encryptedAttributesKeys . length !== typeDefinition . attributesToEncrypt . size ) { this . options . logger . debug ( `The following attributes of saved object "${ descriptorToArray ( descriptor ) }" should have been encrypted: ${ Array . from ( typeDefinition . attributesToEncrypt ) }, but found only: ${ encryptedAttributesKeys }` ) ; } if ( encryptedAttributesKeys . length === 0 ) { return attributes ; } return { ... attributes , ... encryptedAttributes , } ; } param descriptor param attributes param params throws public async encryptAttributes < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const iterator = this . attributesToEncryptIterator < T > ( descriptor , attributes , params ) ; let iteratorResult = iterator . next ( ) ; while ( ! iteratorResult . done ) { const [ attributeValue , encryptionAAD ] = iteratorResult . value ; if ( this . options . primaryCrypto ) { try { iteratorResult = iterator . next ( await this . options . primaryCrypto . encrypt ( attributeValue , encryptionAAD ) ) ; } catch ( err ) { iterator . throw ! ( err ) ; } } else { iterator . throw ! ( new Error ( 'Encryption is disabled because of missing encryption key.' ) ) ; } } return iteratorResult . value ; } param descriptor param attributes param params throws public encryptAttributesSync < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const iterator = this . attributesToEncryptIterator < T > ( descriptor , attributes , params ) ; let iteratorResult = iterator . next ( ) ; while ( ! iteratorResult . done ) { const [ attributeValue , encryptionAAD ] = iteratorResult . value ; if ( this . options . primaryCrypto ) { try { iteratorResult = iterator . next ( this . options . primaryCrypto . encryptSync ( attributeValue , encryptionAAD ) ) ; } catch ( err ) { iterator . throw ! ( err ) ; } } else { iterator . throw ! ( new Error ( 'Encryption is disabled because of missing encryption key.' ) ) ; } } return iteratorResult . value ; } param descriptor param attributes param params throws throws public async decryptAttributes < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const decrypters = this . getDecrypters ( params ?. omitPrimaryEncryptionKey ) ; const iterator = this . attributesToDecryptIterator < T > ( descriptor , attributes , params ) ; let iteratorResult = iterator . next ( ) ; while ( ! iteratorResult . done ) { const [ attributeValue , encryptionAADs ] = iteratorResult . value ; let decryptionError = decrypters . length === 0 ? new Error ( 'Decryption is disabled because of missing decryption keys.' ) : undefined ; const decryptersPerAAD = decrypters . flatMap ( ( decr ) => encryptionAADs . map ( ( aad ) => [ decr , aad ] as [ Crypto , string ] ) ) ; for ( const [ decrypter , encryptionAAD ] of decryptersPerAAD ) { try { iteratorResult = iterator . next ( await decrypter . decrypt ( attributeValue , encryptionAAD ) ) ; decryptionError = undefined ; break ; } catch ( err ) { if ( ! decryptionError ) { decryptionError = err ; } } } if ( decryptionError ) { iterator . throw ! ( decryptionError ) ; } } return iteratorResult . value ; } param descriptor param attributes param params throws throws public decryptAttributesSync < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const decrypters = this . getDecrypters ( params ?. omitPrimaryEncryptionKey ) ; const iterator = this . attributesToDecryptIterator < T > ( descriptor , attributes , params ) ; let iteratorResult = iterator . next ( ) ; while ( ! iteratorResult . done ) { const [ attributeValue , encryptionAADs ] = iteratorResult . value ; let decryptionError = decrypters . length === 0 ? new Error ( 'Decryption is disabled because of missing decryption keys.' ) : undefined ; const decryptersPerAAD = decrypters . flatMap ( ( decr ) => encryptionAADs . map ( ( aad ) => [ decr , aad ] as [ Crypto , string ] ) ) ; for ( const [ decrypter , encryptionAAD ] of decryptersPerAAD ) { try { iteratorResult = iterator . next ( decrypter . decryptSync ( attributeValue , encryptionAAD ) ) ; decryptionError = undefined ; break ; } catch ( err ) { if ( ! decryptionError ) { decryptionError = err ; } } } if ( decryptionError ) { iterator . throw ! ( decryptionError ) ; } } return iteratorResult . value ; } private * attributesToDecryptIterator < T extends Record < string , unknown > > ( descriptor , attributes , params ? ) { const typeDefinition = this . typeDefinitions . get ( descriptor . type ) ; if ( typeDefinition === undefined ) { return attributes ; } const encryptionAADs = [ ] ; const decryptedAttributes = { } ; for ( const attributeName of typeDefinition . attributesToEncrypt ) { const attributeValue = attributes [ attributeName ] ; if ( attributeValue == null ) { continue ; } if ( typeof attributeValue !== 'string' ) { throw new Error ( `Encrypted "${ attributeName }" attribute should be a string, but found ${ typeDetect ( attributeValue ) }` ) ; } if ( ! encryptionAADs . length ) { if ( params ?. isTypeBeingConverted ) { const decryptDescriptors = params . originId ? [ { ... descriptor , id : params . originId } , descriptor ] : [ descriptor ] ; for ( const decryptDescriptor of decryptDescriptors ) { encryptionAADs . push ( this . getAAD ( typeDefinition , decryptDescriptor , attributes ) ) ; if ( descriptor . namespace ) { const { namespace , ... alternateDescriptor } = decryptDescriptor ; encryptionAADs . push ( this . getAAD ( typeDefinition , alternateDescriptor , attributes ) ) ; } } } else { encryptionAADs . push ( this . getAAD ( typeDefinition , descriptor , attributes ) ) ; } } try { decryptedAttributes [ attributeName ] = ( yield [ attributeValue , encryptionAADs ] ) ! ; } catch ( err ) { this . options . logger . error ( `Failed to decrypt "${ attributeName }" attribute: ${ err . message || err }` ) ; throw new EncryptionError ( `Unable to decrypt attribute "${ attributeName }"` , attributeName , EncryptionErrorOperation . Decryption , err ) ; } } const decryptedAttributesKeys = Object . keys ( decryptedAttributes ) ; if ( decryptedAttributesKeys . length !== typeDefinition . attributesToEncrypt . size ) { this . options . logger . debug ( `The following attributes of saved object "${ descriptorToArray ( descriptor ) }" should have been decrypted: ${ Array . from ( typeDefinition . attributesToEncrypt ) }, but found only: ${ decryptedAttributesKeys }` ) ; } if ( decryptedAttributesKeys . length === 0 ) { return attributes ; } return { ... attributes , ... decryptedAttributes , } ; } param typeDefinition param descriptor param attributes private getAAD ( typeDefinition , descriptor , attributes ) { const attributesAAD = { } ; for ( const [ attributeKey , attributeValue ] of Object . entries ( attributes ) ) { if ( ! typeDefinition . shouldBeExcludedFromAAD ( attributeKey ) ) { attributesAAD [ attributeKey ] = attributeValue ; } } if ( Object . keys ( attributesAAD ) . length === 0 ) { this . options . logger . debug ( `The AAD for saved object "${ descriptorToArray ( descriptor ) }" does not include any attributes.` ) ; } return stringify ( [ ... descriptorToArray ( descriptor ) , attributesAAD ] ) ; } param omitPrimaryEncryptionKey private getDecrypters ( omitPrimaryEncryptionKey ? ) { if ( omitPrimaryEncryptionKey ) { if ( ! this . options . decryptionOnlyCryptos || this . options . decryptionOnlyCryptos . length === 0 ) { throw new Error ( `"omitPrimaryEncryptionKey" cannot be set when secondary keys aren't configured.` ) ; } return this . options . decryptionOnlyCryptos ; } return [ ... ( this . options . primaryCrypto ? [ this . options . primaryCrypto ] : [ ] ) , ... ( this . options . decryptionOnlyCryptos ?? [ ] ) , ] ; } }