Skip to content

Commit

Permalink
Ui/replication mgmt/generate token action (#9187)
Browse files Browse the repository at this point in the history
Generate operation token flow from replication DR Secondary. Clicking 'Cancel' on the modal after the operation has started results in cancelling generate operation and restarting the process.
  • Loading branch information
chelshaw authored Jun 17, 2020
1 parent 4f92449 commit b13b1bf
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 13 deletions.
5 changes: 4 additions & 1 deletion ui/app/adapters/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ export default ApplicationAdapter.extend({
},

generateDrOperationToken(data, options) {
const verb = options && options.checkStatus ? 'GET' : 'PUT';
let verb = options && options.checkStatus ? 'GET' : 'PUT';
if (options.cancel) {
verb = 'DELETE';
}
let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`;
if (!data || data.pgp_key || data.attempt) {
// start the generation
Expand Down
39 changes: 39 additions & 0 deletions ui/app/components/shamir-modal-flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @module ShamirModalFlow
* ShamirModalFlow is an extension of the ShamirFlow component that does the Generate Action Token workflow inside of a Modal.
* Please note, this is not an extensive list of the required parameters -- please see ShamirFlow for others
*
* @example
* ```js
* <ShamirModalFlow @onClose={action 'onClose'}>This copy is the main paragraph when the token flow has not started</ShamirModalFlow>
* ```
* @param {function} onClose - This function will be triggered when the modal intends to be closed
*/
import { inject as service } from '@ember/service';
import ShamirFlow from './shamir-flow';
import layout from '../templates/components/shamir-modal-flow';

export default ShamirFlow.extend({
layout,
store: service(),
onClose: () => {},
actions: {
onCancelClose() {
if (this.encoded_token) {
this.send('reset');
} else if (this.generateAction && !this.started) {
if (this.generateStep !== 'chooseMethod') {
this.send('reset');
}
} else {
const adapter = this.get('store').adapterFor('cluster');
adapter.generateDrOperationToken(this.model, { cancel: true });
this.send('reset');
}
this.onClose();
},
onClose() {
this.onClose();
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ import Controller from '@ember/controller';
export default Controller.extend({
queryParams: ['action'],
action: '',
actions: {
onPromote() {
this.transitionToRoute('vault.cluster.replication.mode.index', 'dr');
},
},
});
11 changes: 11 additions & 0 deletions ui/app/styles/core/helpers.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
display: flex;
flex-direction: column;
}
.is-flex-row {
display: flex;
flex-direction: row;
}
.is-flex-v-centered {
display: flex;
align-items: center;
Expand Down Expand Up @@ -152,6 +156,9 @@
.has-bottom-margin-m {
margin-bottom: $spacing-m;
}
.has-bottom-margin-l {
margin-bottom: $spacing-l;
}
.has-top-margin-xl {
margin-top: $spacing-xl;
}
Expand All @@ -163,3 +170,7 @@ ul.bullet {
list-style: disc;
padding-left: $spacing-m;
}

.has-text-semibold {
font-weight: $font-weight-semibold;
}
2 changes: 1 addition & 1 deletion ui/app/templates/components/shamir-flow.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
</form>
{{else}}
<form {{action 'onSubmit' (hash key=key) on="submit"}} id="shamir">
<div class="box is-marginless is-shadowless">
<div class="box is-shadowless">
{{#if errors}}
<div class="box is-shadowless is-marginless no-padding-top is-fullwidth">
{{message-error errors=errors}}
Expand Down
194 changes: 194 additions & 0 deletions ui/app/templates/components/shamir-modal-flow.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<Modal
@title="Generate operation token"
@onClose={{action 'onClose'}}
@isActive={{isActive}}
@type="warning"
@showCloseButton={{true}}
>
<section class="modal-card-body">
{{#if encoded_token}}
<p class="has-bottom-margin-l">
{{#if otp}}
There are three values below. The encoded operation token and the OTP are used in the <span class="has-text-semibold">DR operation token command</span>, which you will need to copy and paste into your CLI in order to generate the operation token. Save your encoded operation token and OTP.
{{else}}
The encoded operation token and the OTP are used in the <span class="has-text-semibold">DR operation token command</span>, which you will need to copy and paste into your CLI in order to generate the operation token. Save your encoded operation token and OTP.
{{/if}}
</p>
<div class="has-bottom-margin-m">
<h4 class="title is-7 has-bottom-padding-m is-fullwidth">
Encoded operation token
</h4>
<div class="message is-list has-copy-button" tabindex="-1">
<HoverCopyButton @copyValue={{encoded_token}} />
<code class="is-word-break" data-test-shamir-encoded-token>{{encoded_token}}</code>
</div>
{{#if otp}}
<h4 class="title is-7 has-bottom-padding-m is-fullwidth">
One time password (OTP)
</h4>
<div class="message is-list has-copy-button" tabindex="-1">
<HoverCopyButton @copyValue={{otp}} />
<code class="is-word-break">{{otp}}</code>
</div>
{{/if}}
<h4 class="title is-7 has-bottom-padding-m is-fullwidth">
DR operation token command
</h4>
<div class="message is-list has-copy-button" tabindex="-1">
{{#let (if otp
(concat 'vault operator generate-root -otp="' otp '" -decode="' encoded_token '"') (concat 'vault operator generate-root -otp="<enter your otp here>" -decode="' encoded_token '"') ) as |cmd|}}
<HoverCopyButton @copyValue={{cmd}} />
<code class="is-word-break">{{cmd}}</code>
{{/let}}
</div>
</div>
<div>
<button type="button" class="button is-primary" {{action 'onCancelClose'}}>
Clear &amp; Close
</button>
</div>
{{else if (and generateAction (not started))}}
<form {{action 'startGenerate' (hash pgp_key=pgp_key) on="submit"}} id="shamir">
{{message-error errors=errors}}
{{#if (eq generateStep 'chooseMethod')}}
<div class="has-bottom-margin-m" data-test-shamir-modal-body>
{{yield}}
</div>
<div class="field is-grouped">
<div class="control is-flex-row">
<button type="button" class="link" {{action (mut generateWithPGP) true}}>
Provide PGP Key
</button>
</div>
<div class="control">
<span class="button auto-width is-white is-static">
or
</span>
</div>
<div class="control">
<button type="submit" class="button is-primary" data-test-generate-token-cta>
Generate operation token
</button>
</div>
</div>
{{/if}}
{{#if (eq generateStep 'providePGPKey')}}
<div class="has-bottom-margin-m">
<p>
Choose a PGP Key from your computer or paste the contents of one in the form below.
This key will be used to Encrypt the generated operation token.
</p>
{{pgp-file index='' key=pgpKeyFile onChange=(action 'setKey')}}
</div>
<div class="field is-grouped">
<div class="control">
<button type="button" class="button" {{action "reset"}}>
Back
</button>
</div>
<div class="control">
<button type="button" disabled={{not pgp_key}} class="button is-primary" {{action "savePGPKey"}}>
Use PGP Key
</button>
</div>
</div>
{{/if}}
{{#if (eq generateStep 'beginGenerationWithPGP')}}
<div>
<p class="has-bottom-margin-m">
Below is the base-64 encoded PGP Key that will be used to encrypt the generated operation token.
Next we'll enter portions of the master key to generate an operation token. Click the "Generate operation token" button to proceed.
</p>
<h4 class="title is-7 has-bottom-padding-m is-fullwidth">
PGP Key {{pgpKeyFile.fileName}}
</h4>
<div class="message is-list has-copy-button" tabindex="-1">
<HoverCopyButton @copyValue={{pgp_key}} />
<code class="is-word-break">{{pgp_key}}</code>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button type="button" class="button" {{action "reset"}}>
Back
</button>
</div>
<div class="control">
<button type="submit" disabled={{and (not pgp_key)}} class="button is-primary">
Generate operation token
</button>
</div>
</div>
{{/if}}
</form>
{{else}}
<form {{action 'onSubmit' (hash key=key) on="submit"}} id="shamir">
<div class="has-bottom-margin-m">
{{#if errors}}
<div class="box is-shadowless is-marginless no-padding-top is-fullwidth">
{{message-error errors=errors}}
</div>
{{/if}}
<div class="box is-shadowless is-marginless no-padding-top is-fullwidth no-padding-sides" data-test-form-text>
{{#if otp}}
<div>
<h4 class="title is-7 has-bottom-padding-m is-fullwidth">
One-time password (OTP)
</h4>
<div class="message is-list has-copy-button" tabindex="-1">
<HoverCopyButton @copyValue={{otp}} />
<code class="is-word-break">{{otp}}</code>
</div>
</div>
<div class="has-text-grey is-size-8 has-bottom-margin-l">
This OTP will be used to encode the generated operation token. <span class="has-text-semibold">Save this</span>, as you will need it later to decode the operation token.
</div>
{{/if}}
<p>
Generate an operation token by entering a portion of the master key. Once all portions are entered, the generated token may be used to manage your secondary Disaster Recovery cluster.
</p>
</div>
<div class="field {{if paddingless 'has-margin-top'}}">
<label for="key" class="is-label">
Master Key Portion
</label>
<div class="control">
{{input class="input"type="password" name="key" value=key data-test-shamir-input=true}}
</div>
</div>
</div>
<div class="has-bottom-margin-m">
<div class="columns is-mobile">
<div class="column is-narrow">
<button
type="submit"
class="button is-primary"
disabled={{loading}}
data-test-shamir-submit=true
>
{{if generateAction "Generate Token" buttonText}}
</button>
</div>
<div class="column is-flex-v-centered is-flex-end">
{{#if (or started hasProgress)}}
<ShamirProgress
@threshold={{threshold}}
@progress={{progress}}
/>
{{/if}}
</div>
</div>
</div>
</form>
{{/if}}
</section>
<footer class="modal-card-foot modal-card-foot-outlined">
<button
class="button is-secondary"
onclick={{action 'onCancelClose'}}
data-test-shamir-modal-cancel-button
>
{{if encoded_token 'Close' 'Cancel'}}
</button>
</footer>
</Modal>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<ReplicationActions
@replicationMode="dr"
@model={{model}}
@onPromote={{action "onPromote"}}
/>
</section>
{{/if}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Actions from './replication-actions-single';
import layout from '../templates/components/replication-action-generate-token';

export default Actions.extend({
layout,
});
4 changes: 2 additions & 2 deletions ui/lib/core/addon/helpers/replication-action-for-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const ACTIONS = {
},
dr: {
primary: ['disable', 'recover', 'reindex', 'demote'],
// TODO: add disable, recover, reindex and update-primary when API is ready
secondary: ['promote', 'update-primary'],
// TODO: add disable, recover, and reindex when API is ready
secondary: ['promote', 'update-primary', 'generate-token'],
bootstrapping: ['disable', 'recover', 'reindex'],
},
};
Expand Down
10 changes: 9 additions & 1 deletion ui/lib/core/addon/mixins/replication-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default Mixin.create({
loading: or('save.isRunning', 'submitSuccess.isRunning'),
onEnable() {},
onDisable() {},
onPromote() {},
submitHandler: task(function*(action, clusterMode, data, event) {
let replicationMode = (data && data.replicationMode) || this.get('replicationMode');
if (event && event.preventDefault) {
Expand All @@ -22,7 +23,11 @@ export default Mixin.create({
data = Object.keys(data).reduce((newData, key) => {
var val = data[key];
if (isPresent(val)) {
newData[key] = val;
if (key === 'dr_operation_token_primary' || key === 'dr_operation_token_promote') {
newData['dr_operation_token'] = val;
} else {
newData[key] = val;
}
}
return newData;
}, {});
Expand Down Expand Up @@ -88,6 +93,9 @@ export default Mixin.create({
if (action === 'disable') {
yield this.onDisable();
}
if (action === 'promote') {
yield this.onPromote();
}
if (action === 'enable') {
/// onEnable is a method available only to route vault.cluster.replication.index
// if action 'enable' is called from vault.cluster.replication.mode.index this method is not called
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="action-block is-rounded" data-test-generate-token-replication>
<div class="action-block-info">
<h4 class="title is-5 is-marginless">
Generate operation token
</h4>
<p>
This token is needed for update and promote actions.
</p>
</div>

<div class="action-block-action">
<button
class="button is-tertiary"
onclick={{action (mut isModalActive) true}}
data-test-replication-action-trigger
>
Generate token
</button>
</div>
</div>


<ShamirModalFlow
@action="generate-dr-operation-token"
@buttonText="Generate token"
@fetchOnInit=true
@generateAction=true
@onClose={{action (mut isModalActive) false}}
@isActive={{isModalActive}}
>
<p>
Updating or promoting this cluster requires an operation token, generated by inputting the master key shares. If you'd like to first encrypt the token with a PGP Key, click "Encrypt with PGP key" below, otherwise we can begin generation of the operation token.
</p>
</ShamirModalFlow>
Loading

0 comments on commit b13b1bf

Please sign in to comment.