Skip to content

Commit

Permalink
UI - ent init (#5428)
Browse files Browse the repository at this point in the history
* allow for enterprise init attributes

* allow moving from init to auth in the init flow on the tutorial machine

* show loading spinner while cluster is unsealing

* use seal-status type to determine the init attrs

* add init acceptance tests

* stored_shares should always be 1

* fix lint

* format template

* remove explicity model attr from init controller
  • Loading branch information
meirish committed Sep 28, 2018
1 parent 9a11700 commit d438d2f
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 50 deletions.
18 changes: 16 additions & 2 deletions ui/app/controllers/vault/cluster/init.js
Expand Up @@ -40,15 +40,29 @@ export default Controller.extend(DEFAULTS, {

actions: {
initCluster(data) {
let isCloudSeal = !!this.model.sealType && this.model.sealType !== 'shamir';
if (data.secret_shares) {
data.secret_shares = parseInt(data.secret_shares);
let shares = parseInt(data.secret_shares, 10);
data.secret_shares = shares;
if (isCloudSeal) {
data.stored_shares = 1;
data.recovery_shares = shares;
}
}
if (data.secret_threshold) {
data.secret_threshold = parseInt(data.secret_threshold);
let threshold = parseInt(data.secret_threshold, 10);
data.secret_threshold = threshold;
if (isCloudSeal) {
data.recovery_threshold = threshold;
}
}
if (!data.use_pgp) {
delete data.pgp_keys;
}
if (data.use_pgp && isCloudSeal) {
data.recovery_pgp_keys = data.pgp_keys;
}

if (!data.use_pgp_for_root) {
delete data.root_token_pgp_key;
}
Expand Down
5 changes: 4 additions & 1 deletion ui/app/machines/tutorial-machine.js
Expand Up @@ -34,7 +34,10 @@ export default {
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-setup' },
},
save: {
on: { TOUNSEAL: 'unseal' },
on: {
TOUNSEAL: 'unseal',
TOLOGIN: 'login',
},
onEntry: { type: 'render', level: 'feature', component: 'wizard/init-save-keys' },
},
unseal: {
Expand Down
6 changes: 2 additions & 4 deletions ui/app/models/cluster.js
Expand Up @@ -12,16 +12,13 @@ export default DS.Model.extend({
name: attr('string'),
status: attr('string'),
standby: attr('boolean'),
type: attr('string'),

needsInit: computed('nodes', 'nodes.[]', function() {
// needs init if no nodes are initialized
return this.get('nodes').isEvery('initialized', false);
}),

type: computed(function() {
return this.constructor.modelName;
}),

unsealed: computed('nodes', 'nodes.{[],@each.sealed}', function() {
// unsealed if there's at least one unsealed node
return !!this.get('nodes').findBy('sealed', false);
Expand All @@ -40,6 +37,7 @@ export default DS.Model.extend({

sealThreshold: alias('leaderNode.sealThreshold'),
sealProgress: alias('leaderNode.progress'),
sealType: alias('leaderNode.type'),
hasProgress: gte('sealProgress', 1),

//replication mode - will only ever be 'unsupported'
Expand Down
6 changes: 1 addition & 5 deletions ui/app/models/node.js
@@ -1,4 +1,3 @@
import { computed } from '@ember/object';
import { alias, and, equal } from '@ember/object/computed';
import DS from 'ember-data';
const { attr } = DS;
Expand All @@ -24,13 +23,10 @@ export default DS.Model.extend({
sealThreshold: alias('t'),
sealNumShares: alias('n'),
version: attr('string'),
type: attr('string'),

//https://www.vaultproject.io/docs/http/sys-leader.html
haEnabled: attr('boolean'),
isSelf: attr('boolean'),
leaderAddress: attr('string'),

type: computed(function() {
return this.constructor.modelName;
}),
});
140 changes: 102 additions & 38 deletions ui/app/templates/vault/cluster/init.hbs
@@ -1,33 +1,56 @@
<SplashPage as |Page|>
{{#if keyData}}
<Page.header>
<h1 class="title is-4">
Vault has been initialized! {{#if (eq keyData.keys.length 1)}}
Here is your key.
{{else}}
Here are your {{pluralize keyData.keys.length "key"}}.
{{/if}}
</h1>
{{#let (or keyData.recovery_keys keyData.keys) as |keyArray|}}
<h1 class="title is-4">
Vault has been initialized!
{{#if (eq keyArray.length 1)}}
Here is your key.
{{else}}
Here are your {{pluralize keyArray.length "key"}}.
{{/if}}
</h1>
{{/let}}
</Page.header>
<Page.content>
<div class="box is-marginless is-shadowless">
<div class="content">
<p>
Please securely distribute the keys below. When the Vault is re-sealed, restarted, or stopped, you must provide at least <strong class="has-text-danger">{{secret_threshold}}</strong> of these keys to unseal it again.
Vault does not store the master key. Without at least <strong class="has-text-danger">{{secret_threshold}}</strong> keys, your Vault will remain permanently sealed.
{{#if keyData.recovery_keys}}
Please securely distribute the keys below. Certain privileged operations in Vault such as rekeying the
barrier or generating a new root token will require you to provide
at least <strong class="has-text-danger">{{secret_threshold}}</strong> of these keys to perform the
operation.
{{else}}
Please securely distribute the keys below. When the Vault is re-sealed, restarted, or stopped, you must
provide at least <strong class="has-text-danger">{{secret_threshold}}</strong> of these keys to unseal it
again.
Vault does not store the master key. Without at least <strong class="has-text-danger">{{secret_threshold}}</strong>
keys, your Vault will remain permanently sealed.
{{/if}}
</p>
</div>
<div class="message is-list is-highlight has-copy-button" tabindex="-1">
<HoverCopyButton @alwaysShow=true @copyValue={{keyData.root_token}} />
<div
class="message is-list is-highlight has-copy-button"
tabindex="-1"
>
<HoverCopyButton
@alwaysShow=true
@copyValue={{keyData.root_token}}
/>
<div class="message-body">
<h4 class="title is-7 is-marginless">
Initial Root Token
</h4>
<code class="is-word-break">{{keyData.root_token}}</code>
</div>
</div>
{{#each (if keyData.keys_base64 keyData.keys_base64 keyData.keys) as |key index| }}
<div class="message is-list has-copy-button" tabindex="-1">
{{#each (or keyData.recovery_keys_base64 keyData.recovery_keys keyData.keys_base64 keyData.keys) as |key index|}}
<div
data-test-key-box
class="message is-list has-copy-button"
tabindex="-1"
>
<HoverCopyButton @copyValue={{key}} />
<div class="message-body">
<h4 class="title is-7 is-marginless">
Expand All @@ -40,16 +63,26 @@
</div>
<div class="box is-marginless is-shadowless">
<div class="field is-grouped-split">
{{#if model.sealed}}
<div class="control">
{{#if (and model.sealed (not keyData.recovery_keys))}}
<div
data-test-advance-button
class="control"
>
{{#link-to 'vault.cluster.unseal' model.name class="button is-primary"}}
Continue to Unseal
Continue to Unseal
{{/link-to}}
</div>
{{else}}
<div class="control">
{{#link-to 'vault.cluster.auth' model.name class="button is-primary"}}
Continue to Authenticate
<div
data-test-advance-button
class="control"
>
{{#link-to 'vault.cluster.auth'
model.name
class=(concat (if model.sealed 'is-loading ' '') 'button is-primary')
disabled=model.sealed
}}
Continue to Authenticate
{{/link-to}}
</div>
{{/if}}
Expand All @@ -60,7 +93,7 @@
@extension="json"
@class="button is-ghost"
@stringify={{true}}
>
>
<ICon @glyph="download" @size=16 /> Download Keys
</DownloadButton>
</div>
Expand All @@ -73,56 +106,84 @@
</h1>
</Page.header>
<Page.content>
<form {{action 'initCluster' (hash
secret_shares=secret_shares
secret_threshold=secret_threshold
pgp_keys=pgp_keys
use_pgp=use_pgp
use_pgp_for_root=use_pgp_for_root
root_token_pgp_key=root_token_pgp_key
<form
{{action 'initCluster' (hash
secret_shares=secret_shares
secret_threshold=secret_threshold
pgp_keys=pgp_keys
use_pgp=use_pgp
use_pgp_for_root=use_pgp_for_root
root_token_pgp_key=root_token_pgp_key
)
on="submit"
}}
id="init"
id="init"
>
<div class="box is-marginless is-shadowless">
<MessageError @errors={{errors}} />
<div class="field">
<label for="key-shares" class="is-label">
<label
for="key-shares"
class="is-label"
>
Key Shares
</label>
<div class="control">
{{input class="input" autocomplete="off" name="key-shares" type="number" step="1" min="1" pattern="[0-9]*" value=secret_shares}}
{{input
data-test-key-shares="true"
class="input"
autocomplete="off"
name="key-shares"
type="number"
step="1"
min="1"
pattern="[0-9]*"
value=secret_shares
}}
</div>
<p class="help has-text-grey">
The number of key shares to split the master key into
</p>
</div>
<div class="field">
<label for="key-threshold" class="is-label">
<label
for="key-threshold"
class="is-label"
>
Key Threshold
</label>
<div class="control">
{{input class="input" autocomplete="off" name="key-threshold" type="number" step="1" min="1" pattern="[0-9]*" value=secret_threshold}}
{{input
data-test-key-threshold="true"
class="input" autocomplete="off"
name="key-threshold"
type="number"
step="1"
min="1"
pattern="[0-9]*"
value=secret_threshold
}}
</div>
<p class="help has-text-grey">
The number of key shares required to reconstruct the master key
</p>
</div>

<ToggleButton
@openLabel="Encrypt Output with PGP"
@closedLabel="Encrypt Output with PGP"
@toggleTarget={{this}}
@toggleAttr="use_pgp"
@class="is-block"
/>
/>
{{#if use_pgp}}
<div class="box init-box">
<p class="help has-text-grey">
The output unseal keys will be encrypted and hex-encoded, in order, with the given public keys.
</p>
<PgpList @listLength={{secret_shares}} @onDataUpdate={{action 'setKeys'}} />
<PgpList
@listLength={{secret_shares}}
@onDataUpdate={{action 'setKeys'}}
/>
</div>
{{/if}}
<ToggleButton
Expand All @@ -137,24 +198,27 @@
<p class="help has-text-grey">
The root unseal key will be encrypted and hex-encoded with the given public key.
</p>
<PgpList @listLength=1 @onDataUpdate={{action 'setRootKey'}} />
<PgpList
@listLength=1
@onDataUpdate={{action 'setRootKey'}}
/>
</div>
{{/if}}
</div>
<div class="box is-marginless is-shadowless">
<button
data-test-init-submit
type="submit"
class="button is-primary {{if loading 'is-loading'}}"
disabled={{loading}}
>
Initialize
</button>

<div class="init-illustration">
{{partial "svg/initialize"}}
</div>
</div>
</form>
</Page.content>
{{/if}}
</SplashPage>
</SplashPage>

0 comments on commit d438d2f

Please sign in to comment.