2023-03-15 16:00:52 +00:00
/ * *
* Copyright ( c ) HashiCorp , Inc .
* SPDX - License - Identifier : MPL - 2.0
* /
2022-04-20 18:40:27 +00:00
import Component from '@glimmer/component' ;
import { action } from '@ember/object' ;
import { inject as service } from '@ember/service' ;
import { tracked } from '@glimmer/tracking' ;
import { KEY _TYPES } from '../../models/keymgmt/key' ;
2022-05-20 15:41:24 +00:00
import { task } from 'ember-concurrency' ;
import { waitFor } from '@ember/test-waiters' ;
2022-04-20 18:40:27 +00:00
/ * *
* @ module KeymgmtDistribute
* KeymgmtDistribute components are used to provide a form to distribute Keymgmt keys to a provider .
*
* @ example
* ` ` ` js
* < KeymgmtDistribute @ backend = "keymgmt" @ key = "my-key" @ provider = "my-kms" / >
* ` ` `
* @ param { string } backend - name of backend , which will be the basis of other store queries
* @ param { string } [ key ] - key is the name of the existing key which is being distributed . Will hide the key field in UI
* @ param { string } [ provider ] - provider is the name of the existing provider which is being distributed to . Will hide the provider field in UI
* /
class DistributionData {
@ tracked key ;
@ tracked provider ;
@ tracked operations ;
@ tracked protection ;
}
const VALID _TYPES _BY _PROVIDER = {
gcpckms : [ 'aes256-gcm96' , 'rsa-2048' , 'rsa-3072' , 'rsa-4096' , 'ecdsa-p256' , 'ecdsa-p384' , 'ecdsa-p521' ] ,
awskms : [ 'aes256-gcm96' ] ,
azurekeyvault : [ 'rsa-2048' , 'rsa-3072' , 'rsa-4096' ] ,
} ;
export default class KeymgmtDistribute extends Component {
@ service store ;
@ service flashMessages ;
@ service router ;
@ tracked keyModel ;
@ tracked isNewKey = false ;
@ tracked providerType ;
@ tracked formData ;
2022-05-20 15:41:24 +00:00
@ tracked formErrors ;
2022-04-20 18:40:27 +00:00
constructor ( ) {
super ( ... arguments ) ;
this . formData = new DistributionData ( ) ;
// Set initial values passed in
this . formData . key = this . args . key || '' ;
this . formData . provider = this . args . provider || '' ;
// Side effects to get types of key or provider passed in
if ( this . args . provider ) {
this . getProviderType ( this . args . provider ) ;
}
if ( this . args . key ) {
this . getKeyInfo ( this . args . key ) ;
}
this . formData . operations = [ ] ;
}
get keyTypes ( ) {
return KEY _TYPES ;
}
get validMatchError ( ) {
if ( ! this . providerType || ! this . keyModel ? . type ) {
return null ;
}
const valid = VALID _TYPES _BY _PROVIDER [ this . providerType ] ? . includes ( this . keyModel . type ) ;
if ( valid ) return null ;
// default to showing error on provider unless @provider (field hidden)
if ( this . args . provider ) {
return {
key : ` This key type is incompatible with the ${ this . providerType } provider. To distribute to this provider, change the key type or choose another key. ` ,
} ;
}
const message = ` This provider is incompatible with the ${ this . keyModel . type } key type. Please choose another provider ` ;
return {
provider : this . args . key ? ` ${ message } . ` : ` ${ message } or change the key type. ` ,
} ;
}
get operations ( ) {
const pt = this . providerType ;
if ( pt === 'awskms' ) {
return [ 'encrypt' , 'decrypt' ] ;
} else if ( pt === 'gcpckms' ) {
const kt = this . keyModel ? . type || '' ;
switch ( kt ) {
case 'aes256-gcm96' :
return [ 'encrypt' , 'decrypt' ] ;
case 'rsa-2048' :
case 'rsa-3072' :
case 'rsa-4096' :
return [ 'decrypt' , 'sign' ] ;
case 'ecdsa-p256' :
case 'ecdsa-p384' :
return [ 'sign' ] ;
default :
return [ 'encrypt' , 'decrypt' , 'sign' , 'verify' , 'wrap' , 'unwrap' ] ;
}
}
return [ 'encrypt' , 'decrypt' , 'sign' , 'verify' , 'wrap' , 'unwrap' ] ;
}
get disableOperations ( ) {
return (
this . validMatchError ||
! this . formData . provider ||
! this . formData . key ||
( this . isNewKey && ! this . keyModel . type )
) ;
}
async getKeyInfo ( keyName , isNew = false ) {
let key ;
if ( isNew ) {
this . isNewKey = true ;
key = this . store . createRecord ( ` keymgmt/key ` , {
backend : this . args . backend ,
id : keyName ,
name : keyName ,
} ) ;
} else {
key = await this . store
. queryRecord ( ` keymgmt/key ` , {
backend : this . args . backend ,
id : keyName ,
recordOnly : true ,
} )
. catch ( ( ) => {
// Key type isn't essential for distributing, so if
// we can't read it for some reason swallow the error
// and allow the API to respond with any key/provider
// type matching errors
} ) ;
}
this . keyModel = key ;
}
async getProviderType ( id ) {
if ( ! id ) {
this . providerType = '' ;
return ;
}
const provider = await this . store
. queryRecord ( 'keymgmt/provider' , {
backend : this . args . backend ,
id ,
} )
. catch ( ( ) => { } ) ;
this . providerType = provider ? . provider ;
}
destroyKey ( ) {
if ( this . isNewKey ) {
// Delete record from store if it was created here
this . keyModel . destroyRecord ( ) . finally ( ( ) => {
this . keyModel = null ;
} ) ;
}
this . isNewKey = false ;
this . keyModel = null ;
}
/ * *
*
* @ param { DistributionData } rawData
* @ returns POJO formatted how the distribution endpoint needs
* /
formatData ( rawData ) {
const { key , provider , operations , protection } = rawData ;
if ( ! key || ! provider || ! operations || operations . length === 0 ) return null ;
return { key , provider , purpose : operations . join ( ',' ) , protection } ;
}
2022-04-26 19:17:42 +00:00
distributeKey ( backend , data ) {
const adapter = this . store . adapterFor ( 'keymgmt/key' ) ;
const { key , provider , purpose , protection } = data ;
2022-04-20 18:40:27 +00:00
return adapter
2022-04-26 19:17:42 +00:00
. distribute ( backend , provider , key , { purpose , protection } )
2022-04-20 18:40:27 +00:00
. then ( ( ) => {
2022-04-26 19:17:42 +00:00
this . flashMessages . success ( ` Successfully distributed key ${ key } to ${ provider } ` ) ;
2022-06-14 17:12:37 +00:00
// update keys on provider model
this . store . clearDataset ( 'keymgmt/key' ) ;
const providerModel = this . store . peekRecord ( 'keymgmt/provider' , provider ) ;
providerModel . fetchKeys ( providerModel . keys ? . meta ? . currentPage || 1 ) ;
2022-04-26 14:23:31 +00:00
this . args . onClose ( ) ;
2022-04-20 18:40:27 +00:00
} )
. catch ( ( e ) => {
2022-05-20 15:41:24 +00:00
this . formErrors = ` ${ e . errors } ` ;
2022-04-20 18:40:27 +00:00
} ) ;
}
@ action
2022-05-20 15:41:24 +00:00
handleProvider ( selection ) {
let providerName = selection [ 0 ] ;
if ( typeof selection === 'string' ) {
// Handles case if no list permissions and fallback component is used
providerName = selection ;
}
this . formData . provider = providerName ;
if ( providerName ) {
this . getProviderType ( providerName ) ;
2022-04-20 18:40:27 +00:00
}
}
@ action
handleKeyType ( evt ) {
this . keyModel . set ( 'type' , evt . target . value ) ;
}
@ action
handleOperation ( evt ) {
const ops = [ ... this . formData . operations ] ;
if ( evt . target . checked ) {
ops . push ( evt . target . id ) ;
} else {
const idx = ops . indexOf ( evt . target . id ) ;
ops . splice ( idx , 1 ) ;
}
this . formData . operations = ops ;
}
@ action
async handleKeySelect ( selected ) {
const selectedKey = selected [ 0 ] || null ;
if ( ! selectedKey ) {
this . formData . key = null ;
return this . destroyKey ( ) ;
}
this . formData . key = selectedKey . id ;
return this . getKeyInfo ( selectedKey . id , selectedKey . isNew ) ;
}
2022-05-20 15:41:24 +00:00
@ task
@ waitFor
* createDistribution ( evt ) {
2022-04-20 18:40:27 +00:00
evt . preventDefault ( ) ;
const { backend } = this . args ;
const data = this . formatData ( this . formData ) ;
if ( ! data ) {
this . flashMessages . danger ( ` Key, provider, and operations are all required ` ) ;
return ;
}
if ( this . isNewKey ) {
2022-04-26 19:17:42 +00:00
try {
2022-05-20 15:41:24 +00:00
yield this . keyModel . save ( ) ;
2022-04-26 19:17:42 +00:00
this . flashMessages . success ( ` Successfully created key ${ this . keyModel . name } ` ) ;
} catch ( e ) {
this . flashMessages . danger ( ` Error creating new key ${ this . keyModel . name } : ${ e . errors } ` ) ;
2022-05-20 15:41:24 +00:00
return ;
2022-04-26 19:17:42 +00:00
}
2022-04-20 18:40:27 +00:00
}
2022-05-20 15:41:24 +00:00
yield this . distributeKey ( backend , data ) ;
// Reload key to get dist info
yield this . store . queryRecord ( ` keymgmt/key ` , {
backend : this . args . backend ,
id : this . keyModel . name ,
} ) ;
2022-04-20 18:40:27 +00:00
}
}