mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
Add Response Wrapping For Secrets (#5664)
This commit is contained in:
@@ -19,6 +19,11 @@ export default ApplicationAdapter.extend({
|
||||
return this._url(backend, path) + `?version=${version}`;
|
||||
},
|
||||
|
||||
urlForQueryRecord(id) {
|
||||
let [backend, path, version] = JSON.parse(id);
|
||||
return this._url(backend, path) + `?version=${version}`;
|
||||
},
|
||||
|
||||
findRecord() {
|
||||
return this._super(...arguments).catch(errorOrModel => {
|
||||
// if it's a real 404, this will be an error, if not
|
||||
@@ -30,6 +35,17 @@ export default ApplicationAdapter.extend({
|
||||
});
|
||||
},
|
||||
|
||||
queryRecord(id, options) {
|
||||
return this.ajax(this.urlForQueryRecord(id), 'GET', options).then(resp => {
|
||||
if (options.wrapTTL) {
|
||||
return resp;
|
||||
}
|
||||
resp.id = id;
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
});
|
||||
},
|
||||
|
||||
urlForCreateRecord(modelName, snapshot) {
|
||||
let backend = snapshot.belongsTo('secret').belongsTo('engine').id;
|
||||
let path = snapshot.attr('path');
|
||||
|
||||
@@ -34,22 +34,29 @@ export default ApplicationAdapter.extend({
|
||||
return url;
|
||||
},
|
||||
|
||||
optionsForQuery(id, action) {
|
||||
optionsForQuery(id, action, wrapTTL) {
|
||||
let data = {};
|
||||
if (action === 'query') {
|
||||
data['list'] = true;
|
||||
data.list = true;
|
||||
}
|
||||
if (wrapTTL) {
|
||||
return { data, wrapTTL };
|
||||
}
|
||||
|
||||
return { data };
|
||||
},
|
||||
|
||||
fetchByQuery(query, action) {
|
||||
const { id, backend } = query;
|
||||
return this.ajax(this.urlForSecret(backend, id), 'GET', this.optionsForQuery(id, action)).then(resp => {
|
||||
resp.id = id;
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
});
|
||||
const { id, backend, wrapTTL } = query;
|
||||
return this.ajax(this.urlForSecret(backend, id), 'GET', this.optionsForQuery(id, action, wrapTTL)).then(
|
||||
resp => {
|
||||
if (wrapTTL) {
|
||||
return resp;
|
||||
}
|
||||
resp.id = id;
|
||||
resp.backend = backend;
|
||||
return resp;
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
query(store, type, query) {
|
||||
|
||||
@@ -17,6 +17,7 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
wizard: service(),
|
||||
router: service(),
|
||||
store: service(),
|
||||
flashMessages: service(),
|
||||
|
||||
// a key model
|
||||
key: null,
|
||||
@@ -31,6 +32,10 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
|
||||
secretData: null,
|
||||
|
||||
wrappedData: null,
|
||||
isWrapping: false,
|
||||
showWrapButton: computed.not('wrappedData'),
|
||||
|
||||
// called with a bool indicating if there's been a change in the secretData
|
||||
onDataChange() {},
|
||||
onRefresh() {},
|
||||
@@ -235,6 +240,53 @@ export default Component.extend(FocusOnInsertMixin, {
|
||||
set(this.modelForData, 'secretData', this.secretData.toJSON());
|
||||
},
|
||||
|
||||
handleWrapClick() {
|
||||
this.set('isWrapping', true);
|
||||
if (this.isV2) {
|
||||
this.store
|
||||
.adapterFor('secret-v2-version')
|
||||
.queryRecord(this.modelForData.id, { wrapTTL: 1800 })
|
||||
.then(resp => {
|
||||
this.set('wrappedData', resp.wrap_info.token);
|
||||
this.flashMessages.success('Secret Successfully Wrapped!');
|
||||
})
|
||||
.catch(() => {
|
||||
this.flashMessages.error('Could Not Wrap Secret');
|
||||
})
|
||||
.finally(() => {
|
||||
this.set('isWrapping', false);
|
||||
});
|
||||
} else {
|
||||
this.store
|
||||
.adapterFor('secret')
|
||||
.queryRecord(null, null, { backend: this.model.backend, id: this.modelForData.id, wrapTTL: 1800 })
|
||||
.then(resp => {
|
||||
this.set('wrappedData', resp.wrap_info.token);
|
||||
this.flashMessages.success('Secret Successfully Wrapped!');
|
||||
})
|
||||
.catch(() => {
|
||||
this.flashMessages.error('Could Not Wrap Secret');
|
||||
})
|
||||
.finally(() => {
|
||||
this.set('isWrapping', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
clearWrappedData() {
|
||||
this.set('wrappedData', null);
|
||||
},
|
||||
|
||||
handleCopySuccess() {
|
||||
this.flashMessages.success('Copied Wrapped Data!');
|
||||
this.send('clearWrappedData');
|
||||
},
|
||||
|
||||
handleCopyError() {
|
||||
this.flashMessages.error('Could Not Copy Wrapped Data');
|
||||
this.send('clearWrappedData');
|
||||
},
|
||||
|
||||
createOrUpdateKey(type, event) {
|
||||
event.preventDefault();
|
||||
let model = this.modelForData;
|
||||
|
||||
@@ -14,6 +14,7 @@ const DEFAULTS = {
|
||||
creation_ttl: null,
|
||||
data: '{\n}',
|
||||
unwrap_data: null,
|
||||
details: null,
|
||||
wrapTTL: null,
|
||||
sum: null,
|
||||
random_bytes: null,
|
||||
@@ -33,6 +34,7 @@ export default Component.extend(DEFAULTS, {
|
||||
algorithm: 'sha2-256',
|
||||
|
||||
tagName: '',
|
||||
unwrapActiveTab: 'data',
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
@@ -76,7 +78,13 @@ export default Component.extend(DEFAULTS, {
|
||||
let props = {};
|
||||
let secret = (resp && resp.data) || resp.auth;
|
||||
if (secret && action === 'unwrap') {
|
||||
props = assign({}, props, { unwrap_data: secret });
|
||||
let details = {
|
||||
'Request ID': resp.request_id,
|
||||
'Lease ID': resp.lease_id || 'None',
|
||||
Renewable: resp.renewable ? 'Yes' : 'No',
|
||||
'Lease Duration': resp.lease_duration || 'None',
|
||||
};
|
||||
props = assign({}, props, { unwrap_data: secret }, { details: details });
|
||||
}
|
||||
props = assign({}, props, secret);
|
||||
if (resp && resp.wrap_info) {
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
padding-top: $spacing-s;
|
||||
}
|
||||
|
||||
.has-padding {
|
||||
padding: $size-10 $size-8;
|
||||
}
|
||||
|
||||
// we want to style the boxes the same everywhere so they
|
||||
// need to be the same font and small
|
||||
.masked-input.masked .masked-value {
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
|
||||
ul {
|
||||
border-color: transparent;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
li {
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
&.is-active a {
|
||||
&.is-active a, &.is-active .tab {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
}
|
||||
&:first-child a {
|
||||
&:first-child a, &:first-child .tab {
|
||||
margin-left: $size-5;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
a, .tab {
|
||||
color: $grey-dark;
|
||||
font-weight: $font-weight-semibold;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<CopyButton
|
||||
@clipboardText={{value}}
|
||||
@class="copy-button button is-compact"
|
||||
@success={{success}}
|
||||
data-test-copy-button
|
||||
>
|
||||
<ICon @glyph="copy" aria-hidden="true" @size=16 />
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<ConfirmAction
|
||||
@buttonClasses="button is-compact is-ghost has-icon-right"
|
||||
@onConfirmAction={{action "deleteKey"}}
|
||||
@confirmMessage={{if isV2
|
||||
@confirmMessage={{if isV2
|
||||
(concat "This will permanently delete " model.id " and all its versions. Are you sure you want to do this?")
|
||||
(concat "Are you sure you want to delete " model.id "?")
|
||||
}}
|
||||
@@ -53,45 +53,102 @@
|
||||
<label for="json" class="has-text-grey">JSON</label>
|
||||
</div>
|
||||
{{#if (and (eq mode 'show') (or canEditV2Secret canEdit))}}
|
||||
<div class="control">
|
||||
{{#let (concat 'vault.cluster.secrets.backend.' (if (eq mode 'show') 'edit' 'show')) as |targetRoute|}}
|
||||
{{#if isV2}}
|
||||
<LinkTo
|
||||
@params={{array targetRoute model.id (query-params version=this.modelForData.version)}}
|
||||
@replace={{true}}
|
||||
class="link link-plain has-text-weight-semibold"
|
||||
data-test-secret-edit="true"
|
||||
>
|
||||
Create new version
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo
|
||||
@params={{array targetRoute model.id}}
|
||||
@replace={{true}}
|
||||
class="link link-plain has-text-weight-semibold"
|
||||
data-test-secret-edit="true"
|
||||
>
|
||||
Edit Secret
|
||||
</LinkTo>
|
||||
{{#unless (and isV2 (or modelForData.destroyed modelForData.deleted))}}
|
||||
<div class="control">
|
||||
<BasicDropdown
|
||||
@class="popup-menu"
|
||||
@horizontalPosition="auto-right"
|
||||
@verticalPosition="below"
|
||||
@onClose={{action "clearWrappedData"}}
|
||||
as |D|
|
||||
>
|
||||
<D.trigger
|
||||
data-test-popup-menu-trigger="true"
|
||||
@class={{concat "link link-plain has-text-weight-semibold" (if D.isOpen " is-active")}}
|
||||
@tagName="button"
|
||||
>
|
||||
Copy Secret
|
||||
</D.trigger>
|
||||
<D.content @class="popup-menu-content is-wide">
|
||||
<nav class="box menu">
|
||||
<ul class="menu-list">
|
||||
<li class="action">
|
||||
<CopyButton
|
||||
@class="link link-plain has-text-weight-semibold is-ghost"
|
||||
@clipboardText={{codemirrorString}}
|
||||
@success={{action (set-flash-message "JSON Copied!")}}
|
||||
data-test-copy-button
|
||||
>
|
||||
Copy JSON
|
||||
</CopyButton>
|
||||
</li>
|
||||
<li class="action">
|
||||
{{#if showWrapButton}}
|
||||
<button
|
||||
class="link link-plain has-text-weight-semibold is-ghost {{if isWrapping "is-loading"}}"
|
||||
type="button"
|
||||
{{action "handleWrapClick"}}
|
||||
data-test-wrap-button
|
||||
disabled={{isWrapping}}
|
||||
>
|
||||
Wrap Secret
|
||||
</button>
|
||||
{{else}}
|
||||
<MaskedInput
|
||||
@class="has-padding"
|
||||
@displayOnly={{true}}
|
||||
@allowCopy={{true}}
|
||||
@value={{wrappedData}}
|
||||
@success={{action "handleCopySuccess"}}
|
||||
@error={{action "handleCopyError"}}
|
||||
/>
|
||||
{{/if}}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</D.content>
|
||||
</BasicDropdown>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="control">
|
||||
{{#if isV2}}
|
||||
<LinkTo
|
||||
@params={{array targetRoute model.id (query-params version=this.modelForData.version)}}
|
||||
@replace={{true}}
|
||||
class="link link-plain has-text-weight-semibold"
|
||||
data-test-secret-edit="true"
|
||||
>
|
||||
Create new version
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<LinkTo
|
||||
@params={{array targetRoute model.id}}
|
||||
@replace={{true}}
|
||||
class="link link-plain has-text-weight-semibold"
|
||||
data-test-secret-edit="true"
|
||||
>
|
||||
Edit Secret
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
{{#if (and (eq @mode "show") this.isV2)}}
|
||||
<div class="control">
|
||||
<SecretVersionMenu @version={{this.modelForData}} />
|
||||
</div>
|
||||
<div class="control">
|
||||
<BasicDropdown
|
||||
@class="popup-menu"
|
||||
@horizontalPosition="auto-right"
|
||||
@verticalPosition="below"
|
||||
<BasicDropdown
|
||||
@class="popup-menu"
|
||||
@horizontalPosition="auto-right"
|
||||
@verticalPosition="below"
|
||||
as |D|
|
||||
>
|
||||
<D.trigger
|
||||
<D.trigger
|
||||
data-test-popup-menu-trigger="true"
|
||||
@class={{concat "popup-menu-trigger button is-ghost has-text-grey" (if D.isOpen " is-active")}}
|
||||
@tagName="button"
|
||||
@class={{concat "popup-menu-trigger button is-ghost has-text-grey" (if D.isOpen " is-active")}}
|
||||
@tagName="button"
|
||||
>
|
||||
History <ICon @glyph="chevron-right" @size="11" />
|
||||
</D.trigger>
|
||||
@@ -103,8 +160,8 @@
|
||||
@mode="versions"
|
||||
@secret={{this.model.id}}
|
||||
@class="has-text-black has-text-weight-semibold has-bottom-shadow"
|
||||
>
|
||||
View version history
|
||||
>
|
||||
View version history
|
||||
</SecretLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -8,19 +8,43 @@
|
||||
|
||||
{{#if unwrap_data}}
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<div class="field">
|
||||
<label class="is-label">
|
||||
Unwrapped data
|
||||
</label>
|
||||
<div class="control">
|
||||
{{json-editor
|
||||
value=(stringify unwrap_data)
|
||||
options=(hash
|
||||
readOnly=true
|
||||
)
|
||||
}}
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
<li role="tab" aria-selected={{if (eq unwrapActiveTab "data") "true" "false"}} class="{{if (eq unwrapActiveTab "data") "is-active"}}">
|
||||
<button class="link link-plain tab has-text-weight-semibold" {{action (mut unwrapActiveTab)}}>Data</button>
|
||||
</li>
|
||||
<li role="tab" aria-selected={{if (eq unwrapActiveTab "data") "true" "false"}} class="{{if (eq unwrapActiveTab "details") "is-active"}}">
|
||||
<button class="link link-plain tab has-text-weight-semibold" {{action (mut unwrapActiveTab) "details"}}>Wrap Details</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{#if (eq unwrapActiveTab "data")}}
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
{{json-editor
|
||||
value=(stringify unwrap_data)
|
||||
options=(hash
|
||||
readOnly=true
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="field box is-fullwidth is-shadowless is-paddingless is-marginless">
|
||||
{{#each-in details as |key detail|}}
|
||||
{{#info-table-row label=key value=key}}
|
||||
{{#if (or (eq detail "No") (eq detail "None"))}}
|
||||
<ICon @class=has-text-grey @size="16" @glyph="cancel-square-outline" /> {{detail}}
|
||||
{{else}}
|
||||
{{#if (eq detail "Yes") }}
|
||||
<ICon @class="has-text-success" @size="16" @glyph="checkmark-circled-outline" />
|
||||
{{/if}}
|
||||
{{detail}}
|
||||
{{/if}}
|
||||
{{/info-table-row}}
|
||||
{{/each-in}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||
<div class="control">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="column">
|
||||
{{#if list.item.deleted}}
|
||||
<span class="has-text-grey is-size-8">
|
||||
<ICon @glyph="false" @size="16" />Deleted
|
||||
<ICon @glyph="cancel-square-outline" @size="16" />Deleted
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#if list.item.destroyed}}
|
||||
@@ -53,7 +53,7 @@
|
||||
@secret={{model.id}}
|
||||
@class="has-text-black has-text-weight-semibold"
|
||||
@queryParams={{query-params version=list.item.version}}
|
||||
>
|
||||
>
|
||||
View version {{list.item.version}}
|
||||
</SecretLink>
|
||||
</li>
|
||||
@@ -63,11 +63,11 @@
|
||||
@secret={{model.id}}
|
||||
@class="has-text-black has-text-weight-semibold"
|
||||
@queryParams={{query-params version=list.item.version}}
|
||||
>
|
||||
>
|
||||
Create new version from {{list.item.version}}
|
||||
</SecretLink>
|
||||
</li>
|
||||
</SecretVersionMenu>
|
||||
</Item.menu>
|
||||
</ListItem>
|
||||
</ListView>
|
||||
</ListView>
|
||||
|
||||
Reference in New Issue
Block a user