import { createHash } from 'crypto' ; import stringify from 'json-stable-stringify' ; import { KibanaRequest , Logger } from 'src/core/server' ; import { SecurityPluginStart } from '../../../../security/server' ; import { ReindexSavedObject , ReindexStatus } from '../../../common/types' ; export type Credential = Record < string , any > ; const getHash = ( reindexOp ) => createHash ( 'sha256' ) . update ( stringify ( { id : reindexOp . id , ... reindexOp . attributes } ) ) . digest ( 'base64' ) ; const getApiKey = async ( { request , security , reindexOpId , apiKeysMap , } ) : Promise < string | undefined > => { try { const apiKeyResult = await security . authc . apiKeys . grantAsInternalUser ( request , { name : `ua_reindex_${ reindexOpId }` , role_descriptors : { } , metadata : { description : 'Created by the Upgrade Assistant for a reindex operation; this can be safely deleted after Kibana is upgraded.' , } , } ) ; if ( apiKeyResult ) { const { api_key : apiKey , id } = apiKeyResult ; apiKeysMap . set ( reindexOpId , id ) ; return Buffer . from ( `${ id }:${ apiKey }` ) . toString ( 'base64' ) ; } } catch ( error ) { return undefined ; } } ; const invalidateApiKey = async ( { apiKeyId , security , log , } ) => { try { await security ?. authc . apiKeys . invalidateAsInternalUser ( { ids : [ apiKeyId ] } ) ; } catch ( error ) { log . debug ( `Error invalidating API key for id ${ apiKeyId }: ${ error . message }` ) ; } } ; /**
 * An in-memory cache for user credentials to be used for reindexing operations. When looking up
 * credentials, the reindex operation must be in the same state it was in when the credentials
 * were stored. This prevents any tampering of the .kibana index by an unpriviledged user from
 * affecting the reindex process.
 */ export interface CredentialStore { get ( reindexOp ) : Credential | undefined ; set ( params ) : Promise < void > ; update ( params ) : Promise < void > ; clear ( ) : void ; } export const credentialStoreFactory = ( logger ) : CredentialStore => { const credMap = new Map < string , Credential > ( ) ; const apiKeysMap = new Map < string , string > ( ) ; const log = logger . get ( 'credential_store' ) ; return { get ( reindexOp ) { return credMap . get ( getHash ( reindexOp ) ) ; } , async set ( { reindexOp , request , security , } ) { const areApiKeysEnabled = ( await security ?. authc . apiKeys . areAPIKeysEnabled ( ) ) ?? false ; if ( areApiKeysEnabled ) { const apiKey = await getApiKey ( { request , security : security ! , reindexOpId : reindexOp . id , apiKeysMap , } ) ; if ( apiKey ) { credMap . set ( getHash ( reindexOp ) , { ... request . headers , authorization : `ApiKey ${ apiKey }` , } ) ; return ; } } credMap . set ( getHash ( reindexOp ) , request . headers ) ; } , async update ( { reindexOp , security , credential , } ) { if ( reindexOp . attributes . status === ReindexStatus . completed ) { const apiKeyId = apiKeysMap . get ( reindexOp . id ) ; if ( apiKeyId ) { await invalidateApiKey ( { apiKeyId , security , log } ) ; apiKeysMap . delete ( reindexOp . id ) ; return ; } } credMap . set ( getHash ( reindexOp ) , credential ) ; } , clear ( ) { for ( const k of credMap . keys ( ) ) { credMap . delete ( k ) ; } } , } ; } ;