KMSE Wizard Steps (#15171)

* fixes issues in key-edit component

* adds capabilities checks for keys and providers

* adds distribute component to key and provider edit

* adds wizard steps for kmse
This commit is contained in:
Jordan Reimer 2022-04-26 13:17:42 -06:00 committed by GitHub
parent bf2667bd2c
commit 6d1ddf36a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 157 additions and 25 deletions

View file

@ -33,6 +33,7 @@ export default class KeymgmtDistribute extends Component {
@service store;
@service flashMessages;
@service router;
@service wizard;
@tracked keyModel;
@tracked isNewKey = false;
@ -53,6 +54,14 @@ export default class KeymgmtDistribute extends Component {
this.getKeyInfo(this.args.key);
}
this.formData.operations = [];
this.updateWizard('nextStep');
}
updateWizard(key) {
// wizard will pause unless we manually continue it -- verify that keymgmt tutorial is in progress
if (this.wizard[key] === 'distribute') {
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', 'keymgmt');
}
}
get keyTypes() {
@ -175,12 +184,15 @@ export default class KeymgmtDistribute extends Component {
return { key, provider, purpose: operations.join(','), protection };
}
distributeKey(backend, kms, key, data) {
let adapter = this.store.adapterFor('keymgmt/key');
distributeKey(backend, data) {
const adapter = this.store.adapterFor('keymgmt/key');
const { key, provider, purpose, protection } = data;
return adapter
.distribute(backend, kms, key, data)
.distribute(backend, provider, key, { purpose, protection })
.then(() => {
this.flashMessages.success(`Successfully distributed key ${key} to ${kms}`);
this.flashMessages.success(`Successfully distributed key ${key} to ${provider}`);
// move wizard forward if tutorial is in progress
this.updateWizard('featureState');
this.args.onClose();
})
.catch((e) => {
@ -233,15 +245,13 @@ export default class KeymgmtDistribute extends Component {
return;
}
if (this.isNewKey) {
this.keyModel
.save()
.then(() => {
this.flashMessages.success(`Successfully created key ${this.keyModel.name}`);
})
.catch((e) => {
this.flashMessages.danger(`Error creating new key ${this.keyModel.name}: ${e.errors}`);
});
try {
await this.keyModel.save();
this.flashMessages.success(`Successfully created key ${this.keyModel.name}`);
} catch (e) {
this.flashMessages.danger(`Error creating new key ${this.keyModel.name}: ${e.errors}`);
}
}
this.distributeKey(backend, 'example-kms', 'example-key', this.formatData(this.formData));
this.distributeKey(backend, data);
}
}

View file

@ -0,0 +1,27 @@
import Component from '@glimmer/component';
export default class WizardSecretsKeymgmtComponent extends Component {
get headerText() {
return {
provider: 'Creating a provider',
displayProvider: 'Distributing a key',
distribute: 'Creating a key',
}[this.args.featureState];
}
get body() {
return {
provider: 'This process connects an external provider to Vault. You will need its credentials.',
displayProvider: 'A key can now be created and distributed to this destination.',
distribute: 'This process creates a key and distributes it to your provider.',
}[this.args.featureState];
}
get instructions() {
return {
provider: 'Enter your provider details and click “Create provider“.',
displayProvider: 'Click “Distribute key” in the toolbar.',
distribute: 'Enter your key details and click “Distribute key”.',
}[this.args.featureState];
}
}

View file

@ -12,6 +12,10 @@ export default Controller.extend({
if (SUPPORTED_BACKENDS.includes(type)) {
if (type === 'kmip') {
transition = this.transitionToRoute('vault.cluster.secrets.backend.kmip.scopes', path);
} else if (type === 'keymgmt') {
transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path, {
queryParams: { tab: 'provider' },
});
} else {
transition = this.transitionToRoute('vault.cluster.secrets.backend.index', path);
}

View file

@ -48,6 +48,7 @@ export const FEATURE_MACHINE_STEPS = {
secret: 5,
},
role: 7,
provider: 8,
},
policies: 5,
replication: 2,

View file

@ -51,6 +51,9 @@ export default {
encryption: {
cond: (type) => type === 'transit',
},
provider: {
cond: (type) => type === 'keymgmt',
},
},
},
},
@ -126,6 +129,33 @@ export default {
CONTINUE: 'display',
},
},
provider: {
onEntry: [
{ type: 'render', level: 'step', component: 'wizard/secrets-keymgmt' },
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
],
on: {
CONTINUE: 'displayProvider',
},
},
displayProvider: {
onEntry: [
{ type: 'render', level: 'step', component: 'wizard/secrets-keymgmt' },
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
],
on: {
CONTINUE: 'distribute',
},
},
distribute: {
onEntry: [
{ type: 'render', level: 'step', component: 'wizard/secrets-keymgmt' },
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
],
on: {
CONTINUE: 'display',
},
},
display: {
onEntry: [
{ type: 'render', level: 'step', component: 'wizard/secrets-display' },
@ -149,6 +179,18 @@ export default {
cond: (type) => type === 'transit',
actions: [{ type: 'routeTransition', params: ['vault.cluster.secrets.backend.create-root'] }],
},
provider: {
cond: (type) => type === 'keymgmt',
actions: [
{
type: 'routeTransition',
params: [
'vault.cluster.secrets.backend.create-root',
{ queryParams: { itemType: 'provider' } },
],
},
],
},
},
},
},

View file

@ -29,6 +29,7 @@ const transformModel = (queryParams) => {
export default EditBase.extend({
wizard: service(),
createModel(transition) {
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
let modelType = this.modelType(backend, null, { queryParams: transition.to.queryParams });
@ -57,6 +58,10 @@ export default EditBase.extend({
},
model(params, transition) {
// wizard will pause unless we manually continue it -- verify that keymgmt tutorial is in progress
if (params.itemType === 'provider' && this.wizard.nextStep === 'provider') {
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', 'keymgmt');
}
return hash({
secret: this.createModel(transition),
capabilities: {},

View file

@ -9,6 +9,8 @@ import { encodePath, normalizePath } from 'vault/utils/path-encoding-helpers';
export default Route.extend(UnloadModelRoute, {
pathHelp: service('path-help'),
wizard: service(),
secretParam() {
let { secret } = this.paramsFor(this.routeName);
return secret ? normalizePath(secret) : '';
@ -211,8 +213,16 @@ export default Route.extend(UnloadModelRoute, {
let secretModel = this.store.peekRecord(modelType, secretId);
return secretModel;
},
// wizard will pause unless we manually continue it
updateWizard(params) {
// verify that keymgmt tutorial is in progress
if (params.itemType === 'provider' && this.wizard.nextStep === 'displayProvider') {
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', 'keymgmt');
}
},
async model(params, { to: { queryParams } }) {
this.updateWizard(params);
let secret = this.secretParam();
let backend = this.enginePathParam();
let modelType = this.modelType(backend, secret, { queryParams });

View file

@ -18,7 +18,7 @@
</PageHeader>
{{#if this.isDistributing}}
<Keymgmt::Distribute @backend={{@model.backend}} @key={{@model}} @onClose={{fn (mut this.isDistributing) false}} />
<Keymgmt::Distribute @backend={{@model.backend}} @key={{@model.id}} @onClose={{fn (mut this.isDistributing) false}} />
{{else}}
{{#if (eq this.mode "show")}}
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless" data-test-keymgmt-key-toolbar>

View file

@ -17,7 +17,7 @@
</PageHeader>
{{#if this.isDistributing}}
<Keymgmt::Distribute @backend={{@model.backend}} @provider={{@model}} @onClose={{fn (mut this.isDistributing) false}} />
<Keymgmt::Distribute @backend={{@model.backend}} @provider={{@model.id}} @onClose={{fn (mut this.isDistributing) false}} />
{{else}}
{{#if this.isShowing}}
<div class="tabs-container box is-sideless is-fullwidth is-paddingless is-marginless">

View file

@ -0,0 +1,11 @@
<WizardSection
@headerText="Key Management"
@headerIcon="key"
@docText="Docs: Key Management Secrets Engine"
@docPath="/docs/secrets/key-management"
>
<p>
Key Management is a secrets engine that allows key generation, lifecycle management, and secure distribution of
cryptographic keys into cloud key management services.
</p>
</WizardSection>

View file

@ -6,6 +6,7 @@
actionText=this.actionText
nextFeature=this.nextFeature
nextStep=this.nextStep
featureState=this.wizard.featureState
needsConnection=this.needsConnection
needsEncryption=this.needsEncryption
isSupported=this.isSupported

View file

@ -1,12 +1,17 @@
<WizardSection @headerText="Your Secrets Engine" @instructions="Click on the link to add a {{@nextStep}} in the page header">
<p>
{{#if @needsEncryption}}
The Transit Secrets Engine uses encryption keys to provide "encryption as a service". Click on "Create Encryption Key"
at the top to create one.
{{/if}}
{{#if @needsConnection}}
Now that the engine has been mounted, lets connect a
{{@mountSubtype}}.
{{#if (eq @mountSubtype "keymgmt")}}
This secrets engine manages keys and distributes them to external destinations. We recommend that you create a provider
to which you can distribute keys.
{{else}}
{{#if @needsEncryption}}
The Transit Secrets Engine uses encryption keys to provide "encryption as a service". Click on "Create Encryption
Key" at the top to create one.
{{/if}}
{{#if @needsConnection}}
Now that the engine has been mounted, lets connect a
{{@mountSubtype}}.
{{/if}}
{{/if}}
</p>
</WizardSection>

View file

@ -1,7 +1,16 @@
{{#if @isSupported}}
<WizardSection @headerText={{unless @actionText "All set!" "Generate Credential"}}>
<WizardSection
@headerText={{if
(eq @mountSubtype "keymgmt")
"Your key and provider"
(unless @actionText "All set!" "Generate Credential")
}}
>
<p>
{{#if @actionText}}
{{#if (eq @mountSubtype "keymgmt")}}
Your key and your provider have been created and connected. From here, you can click the key name to view the key
Youre now ready to start using the secrets engine.
{{else if @actionText}}
Here is your generated credential. As you can see, we can only show the credential once, so you'll want to be sure to
save it. If you need another credential in the future, just come back and generate a new one.
{{else}}
@ -20,7 +29,7 @@
</p>
</WizardSection>
{{/if}}
<WizardSection @headerText="Want to start again or move on?" @class="wizard-details">
<WizardSection @headerText="Want to start again or move on?" @class="wizard-details has-bottom-margin-l">
{{#if @isSupported}}
<button type="button" class="button next-feature-step" {{action @onRepeat}}>
Create another

View file

@ -4,6 +4,7 @@
@docText="Docs: Secrets Engines"
@docPath="/docs/secrets/index.html"
@instructions='Fill in the details for your engine and click "Enable Engine"'
@class="has-bottom-margin-l"
>
<p>
Good choice! Now you can customize your engine with a name and description that makes sense for your team, as well as

View file

@ -4,6 +4,7 @@
@docText="Docs: Secrets Engines"
@docPath="/docs/secrets/index.html"
@instructions='Select an engine and click "Next"'
@class="has-bottom-margin-l"
>
<p>
Vault is all about managing secrets, so let's set up your first Secrets Engine. You can use a static engine to store your

View file

@ -0,0 +1,5 @@
<WizardSection @headerText={{this.headerText}} @instructions={{this.instructions}}>
<p>
{{this.body}}
</p>
</WizardSection>