UI secondary dr recovery token fix (#5818)
* use the OTP that the server provides instead of generating one in the JS client * fix button styling * differentiate between OTP and encoded token and encrypted token in the template
This commit is contained in:
parent
33776b89c2
commit
610fe599c2
|
@ -180,7 +180,7 @@ export default ApplicationAdapter.extend({
|
||||||
generateDrOperationToken(data, options) {
|
generateDrOperationToken(data, options) {
|
||||||
const verb = options && options.checkStatus ? 'GET' : 'PUT';
|
const verb = options && options.checkStatus ? 'GET' : 'PUT';
|
||||||
let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`;
|
let url = `${this.buildURL()}/replication/dr/secondary/generate-operation-token/`;
|
||||||
if (!data || data.pgp_key || data.otp) {
|
if (!data || data.pgp_key || data.attempt) {
|
||||||
// start the generation
|
// start the generation
|
||||||
url = url + 'attempt';
|
url = url + 'attempt';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { gt } from '@ember/object/computed';
|
||||||
import { camelize } from '@ember/string';
|
import { camelize } from '@ember/string';
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
import base64js from 'base64-js';
|
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
key: null,
|
key: null,
|
||||||
|
@ -62,18 +61,18 @@ export default Component.extend(DEFAULTS, {
|
||||||
hasProgress: gt('progress', 0),
|
hasProgress: gt('progress', 0),
|
||||||
|
|
||||||
actionSuccess(resp) {
|
actionSuccess(resp) {
|
||||||
let { onUpdate, isComplete, onShamirSuccess, thresholdPath } = this.getProperties(
|
let { onUpdate, isComplete, onShamirSuccess, thresholdPath } = this;
|
||||||
'onUpdate',
|
|
||||||
'isComplete',
|
|
||||||
'onShamirSuccess',
|
|
||||||
'thresholdPath'
|
|
||||||
);
|
|
||||||
let threshold = get(resp, thresholdPath);
|
let threshold = get(resp, thresholdPath);
|
||||||
let props = {
|
let props = {
|
||||||
...resp,
|
...resp,
|
||||||
threshold,
|
threshold,
|
||||||
};
|
};
|
||||||
this.stopLoading();
|
this.stopLoading();
|
||||||
|
// if we have an OTP, but update doesn't include one,
|
||||||
|
// we don't want to null it out
|
||||||
|
if (this.otp && !props.otp) {
|
||||||
|
delete props.otp;
|
||||||
|
}
|
||||||
this.setProperties(props);
|
this.setProperties(props);
|
||||||
onUpdate(props);
|
onUpdate(props);
|
||||||
if (isComplete(props)) {
|
if (isComplete(props)) {
|
||||||
|
@ -91,19 +90,11 @@ export default Component.extend(DEFAULTS, {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
generateStep: computed('generateWithPGP', 'haveSavedPGPKey', 'otp', 'pgp_key', function() {
|
generateStep: computed('generateWithPGP', 'haveSavedPGPKey', 'pgp_key', function() {
|
||||||
let { generateWithPGP, otp, pgp_key, haveSavedPGPKey } = this.getProperties(
|
let { generateWithPGP, pgp_key, haveSavedPGPKey } = this;
|
||||||
'generateWithPGP',
|
if (!generateWithPGP && !pgp_key) {
|
||||||
'otp',
|
|
||||||
'pgp_key',
|
|
||||||
'haveSavedPGPKey'
|
|
||||||
);
|
|
||||||
if (!generateWithPGP && !pgp_key && !otp) {
|
|
||||||
return 'chooseMethod';
|
return 'chooseMethod';
|
||||||
}
|
}
|
||||||
if (otp) {
|
|
||||||
return 'beginGenerationWithOTP';
|
|
||||||
}
|
|
||||||
if (generateWithPGP) {
|
if (generateWithPGP) {
|
||||||
if (pgp_key && haveSavedPGPKey) {
|
if (pgp_key && haveSavedPGPKey) {
|
||||||
return 'beginGenerationWithPGP';
|
return 'beginGenerationWithPGP';
|
||||||
|
@ -133,7 +124,7 @@ export default Component.extend(DEFAULTS, {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
otp: data.otp,
|
attempt: data.attempt,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -144,6 +135,7 @@ export default Component.extend(DEFAULTS, {
|
||||||
this.set('loading', true);
|
this.set('loading', true);
|
||||||
const adapter = this.get('store').adapterFor('cluster');
|
const adapter = this.get('store').adapterFor('cluster');
|
||||||
const method = adapter[action];
|
const method = adapter[action];
|
||||||
|
|
||||||
method
|
method
|
||||||
.call(adapter, data, { checkStatus })
|
.call(adapter, data, { checkStatus })
|
||||||
.then(resp => this.actionSuccess(resp), (...args) => this.actionError(...args));
|
.then(resp => this.actionSuccess(resp), (...args) => this.actionError(...args));
|
||||||
|
@ -164,15 +156,12 @@ export default Component.extend(DEFAULTS, {
|
||||||
},
|
},
|
||||||
|
|
||||||
startGenerate(data) {
|
startGenerate(data) {
|
||||||
|
if (this.generateAction) {
|
||||||
|
data.attempt = true;
|
||||||
|
}
|
||||||
this.attemptProgress(this.extractData(data));
|
this.attemptProgress(this.extractData(data));
|
||||||
},
|
},
|
||||||
|
|
||||||
generateOTP() {
|
|
||||||
const bytes = new window.Uint8Array(16);
|
|
||||||
window.crypto.getRandomValues(bytes);
|
|
||||||
this.set('otp', base64js.fromByteArray(bytes));
|
|
||||||
},
|
|
||||||
|
|
||||||
setKey(_, keyFile) {
|
setKey(_, keyFile) {
|
||||||
this.set('pgp_key', keyFile.value);
|
this.set('pgp_key', keyFile.value);
|
||||||
this.set('pgpKeyFile', keyFile);
|
this.set('pgpKeyFile', keyFile);
|
||||||
|
|
|
@ -13,8 +13,7 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
padding: $size-10 $size-8;
|
padding: $size-10 $size-8;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: background-color $speed, border-color $speed, box-shadow $speed,
|
transition: background-color $speed, border-color $speed, box-shadow $speed, color $speed;
|
||||||
color $speed;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
&.is-icon {
|
&.is-icon {
|
||||||
|
@ -45,7 +44,7 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
||||||
|
|
||||||
@each $name, $pair in $colors {
|
@each $name, $pair in $colors {
|
||||||
$color: nth($pair, 1);
|
$color: nth($pair, 1);
|
||||||
@if $name == "primary" {
|
@if $name == 'primary' {
|
||||||
$color: $blue;
|
$color: $blue;
|
||||||
}
|
}
|
||||||
$color-invert: nth($pair, 2);
|
$color-invert: nth($pair, 2);
|
||||||
|
@ -211,8 +210,10 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.auto-width,
|
||||||
.button .icon.auto-width {
|
.button .icon.auto-width {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
min-width: auto;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,29 @@
|
||||||
<HoverCopyButton @copyValue={{encoded_token}} />
|
<HoverCopyButton @copyValue={{encoded_token}} />
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
<h4 class="title is-7 is-marginless">
|
<h4 class="title is-7 is-marginless">
|
||||||
|
{{#if otp}}
|
||||||
Encoded Operation Token
|
Encoded Operation Token
|
||||||
|
{{else}}
|
||||||
|
Encrypted Operation Token
|
||||||
|
{{/if}}
|
||||||
</h4>
|
</h4>
|
||||||
<code class="is-word-break">{{encoded_token}}</code>
|
<code class="is-word-break">{{encoded_token}}</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
{{#if otp}}
|
||||||
If you entered a One Time Password, you can use the Vault CLI to decode the Token:
|
|
||||||
</p>
|
|
||||||
<div class="message is-list has-copy-button" tabindex="-1">
|
<div class="message is-list has-copy-button" tabindex="-1">
|
||||||
{{#with (if otp
|
<HoverCopyButton @copyValue={{otp}} />
|
||||||
|
<div class="message-body">
|
||||||
|
<h4 class="title is-7 is-marginless">
|
||||||
|
One Time Password (otp)
|
||||||
|
</h4>
|
||||||
|
<code class="is-word-break">{{otp}}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="message is-list has-copy-button" tabindex="-1">
|
||||||
|
{{#let
|
||||||
(concat 'vault operator generate-root -otp="' otp '" -decode="' encoded_token '"')
|
(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|}}
|
||||||
) as |cmd|}}
|
|
||||||
<HoverCopyButton @copyValue={{cmd}} />
|
<HoverCopyButton @copyValue={{cmd}} />
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
<h4 class="title is-7 is-marginless">
|
<h4 class="title is-7 is-marginless">
|
||||||
|
@ -24,8 +34,9 @@
|
||||||
</h4>
|
</h4>
|
||||||
<code class="is-word-break">{{cmd}}</code>
|
<code class="is-word-break">{{cmd}}</code>
|
||||||
</div>
|
</div>
|
||||||
{{/with}}
|
{{/let}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="box is-marginless is-shadowless">
|
<div class="box is-marginless is-shadowless">
|
||||||
<button type="button" class="button" {{action 'reset'}}>
|
<button type="button" class="button" {{action 'reset'}}>
|
||||||
|
@ -33,14 +44,13 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{else if (and generateAction (not started))}}
|
{{else if (and generateAction (not started))}}
|
||||||
<form {{action 'startGenerate' (hash otp=otp pgp_key=pgp_key) on="submit"}} id="shamir">
|
<form {{action 'startGenerate' (hash pgp_key=pgp_key) on="submit"}} id="shamir">
|
||||||
{{message-error errors=errors}}
|
{{message-error errors=errors}}
|
||||||
{{#if (eq generateStep 'chooseMethod')}}
|
{{#if (eq generateStep 'chooseMethod')}}
|
||||||
<div class="box is-marginless is-shadowless">
|
<div class="box is-marginless is-shadowless">
|
||||||
<p>
|
<p>
|
||||||
Updating or promoting this cluster requires an operation token. Let's generate one by
|
Updating or promoting this cluster requires an operation token. Let's generate one by
|
||||||
inputting the master key shares. To get started you'll need to generate a One Time Password
|
inputting the master key shares. If you'd like to encrypt the token with a PGP Key, please click "Provide PGP Key" below, otherwise we can begin generation of the Operation Token.
|
||||||
(OTP) or provide a PGP Key to encrypt the resultant operation token.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="box is-shadowless field is-grouped is-grouped-centered">
|
<div class="box is-shadowless field is-grouped is-grouped-centered">
|
||||||
|
@ -50,20 +60,18 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<span class="button is-white is-static">
|
<span class="button auto-width is-white is-static">
|
||||||
or
|
or
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="button" class="button is-primary" {{action "generateOTP"}}>
|
<button type="submit" class="button is-primary">
|
||||||
Generate OTP
|
Generate Operation Token
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if (eq generateStep 'providePGPKey')}}
|
{{#if (eq generateStep 'providePGPKey')}}
|
||||||
|
|
||||||
<div class="box is-marginless is-shadowless">
|
<div class="box is-marginless is-shadowless">
|
||||||
<p>
|
<p>
|
||||||
Choose a PGP Key from your computer or paste the contents of one in the form below.
|
Choose a PGP Key from your computer or paste the contents of one in the form below.
|
||||||
|
@ -71,7 +79,6 @@
|
||||||
</p>
|
</p>
|
||||||
{{pgp-file index='' key=pgpKeyFile onChange=(action 'setKey')}}
|
{{pgp-file index='' key=pgpKeyFile onChange=(action 'setKey')}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field is-grouped box is-marginless is-shadowless">
|
<div class="field is-grouped box is-marginless is-shadowless">
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="button" class="button" {{action "reset"}}>
|
<button type="button" class="button" {{action "reset"}}>
|
||||||
|
@ -108,39 +115,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="submit" disabled={{and (not pgp_key) (not otp)}} class="button is-primary">
|
<button type="submit" disabled={{and (not pgp_key)}} class="button is-primary">
|
||||||
Generate Operation Token
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if (eq generateStep 'beginGenerationWithOTP')}}
|
|
||||||
<div class="box is-marginless is-shadowless">
|
|
||||||
<p>
|
|
||||||
Below is the generated OTP. This will be used to encrypt the generated Operation Token.
|
|
||||||
<em class="has-text-danger has-text-weight-semibold">
|
|
||||||
Make sure to save this, as you will need it later to decrypt the Operation Token.
|
|
||||||
</em>
|
|
||||||
Next we'll enter portions of the master key to generate an Operation Token. Click the "Generate Operation Token" button to proceed.
|
|
||||||
</p>
|
|
||||||
<div class="message is-list has-copy-button" tabindex="-1">
|
|
||||||
<HoverCopyButton @copyValue={{otp}} />
|
|
||||||
<div class="message-body">
|
|
||||||
<h4 class="title is-7 is-marginless">
|
|
||||||
One Time Password
|
|
||||||
</h4>
|
|
||||||
<code class="is-word-break">{{otp}}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="field is-grouped box is-marginless is-shadowless">
|
|
||||||
<div class="control">
|
|
||||||
<button type="button" class="button" {{action "reset"}}>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<button type="submit" disabled={{and (not pgp_key) (not otp)}} class="button is-primary">
|
|
||||||
Generate Operation Token
|
Generate Operation Token
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue