Skip to content

Commit

Permalink
Ui kv preflight endpoints (#4439)
Browse files Browse the repository at this point in the history
* remove unused response-wrapping route and controller

* move to using the internal mounts endpoint for the secrets list and individual engine lookup

* remove errors about sys/mounts access because we don't need it anymore 🎉

* use modelFor instead of peekRecord for looking up the secret-engine

* remove test because we removed that error page - in the worst case scenario, a user will only have access to cubbyhole, but will see that in the secrets engines list

* make the dev CSP the same as the Go CSP

* update serializer to handle SSH responses as well as new engine fetches

* back out some changes to ttl-picker and field test object so that tests pass

* get rid of trailing space in the secret engine link

* add secrets-engine  adapater tests for new query behavior
  • Loading branch information
meirish authored Apr 24, 2018
1 parent b12fa85 commit 1489293
Show file tree
Hide file tree
Showing 20 changed files with 153 additions and 135 deletions.
31 changes: 8 additions & 23 deletions ui/app/adapters/secret-engine.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
import Ember from 'ember';
import ApplicationAdapter from './application';
import DS from 'ember-data';

export default ApplicationAdapter.extend({
url(path) {
const url = `${this.buildURL()}/mounts`;
return path ? url + '/' + path : url;
},

pathForType(type) {
let path;
switch (type) {
case 'cluster':
path = 'clusters';
break;
case 'secret-engine':
path = 'mounts';
break;
default:
path = Ember.String.pluralize(type);
break;
}
return path;
pathForType() {
return 'mounts';
},

query() {
return this.ajax(this.url(), 'GET').catch(e => {
if (e instanceof DS.AdapterError) {
Ember.set(e, 'policyPath', 'sys/mounts');
}

throw e;
});
query(store, type, query) {
let url = `/${this.urlPrefix()}/internal/ui/mounts`;
if (query.path) {
url = `${url}/${query.path}`;
}
return this.ajax(url, 'GET');
},

createRecord(store, type, snapshot) {
Expand Down
9 changes: 0 additions & 9 deletions ui/app/controllers/vault/cluster/response-wrapping.js

This file was deleted.

1 change: 0 additions & 1 deletion ui/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ Router.map(function() {
});
});

this.route('response-wrapping');
this.route('not-found', { path: '/*path' });
});
this.route('not-found', { path: '/*path' });
Expand Down
6 changes: 1 addition & 5 deletions ui/app/routes/vault/cluster/secrets.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import Ember from 'ember';
import ClusterRoute from 'vault/mixins/cluster-route';

export default Ember.Route.extend(ClusterRoute, {
model() {
return this.store.query('secret-engine', {});
},
});
export default Ember.Route.extend(ClusterRoute);
28 changes: 20 additions & 8 deletions ui/app/routes/vault/cluster/secrets/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,31 @@ import Ember from 'ember';
const { inject } = Ember;
export default Ember.Route.extend({
flashMessages: inject.service(),
beforeModel(transition) {
const target = transition.targetName;
const { backend } = this.paramsFor(this.routeName);
const backendModel = this.store.peekRecord('secret-engine', backend);
const type = backendModel && backendModel.get('type');
if (type === 'kv' && backendModel.get('options.version') === 2) {
model(params) {
let { backend } = params;
return this.store
.query('secret-engine', {
path: backend,
})
.then(model => {
if (model) {
return model.get('firstObject');
}
});
},

afterModel(model, transition) {
let target = transition.targetName;
let path = model && model.get('path');
let type = model && model.get('type');
if (type === 'kv' && model.get('options.version') === 2) {
this.get('flashMessages').stickyInfo(
`"${backend}" is a newer version of the KV backend. The Vault UI does not currently support the additional versioning features. All actions taken through the UI in this engine will operate on the most recent version of a secret.`
`"${path}" is a newer version of the KV backend. The Vault UI does not currently support the additional versioning features. All actions taken through the UI in this engine will operate on the most recent version of a secret.`
);
}

if (target === this.routeName) {
return this.replaceWith('vault.cluster.secrets.backend.list-root', backend);
return this.replaceWith('vault.cluster.secrets.backend.list-root', path);
}
},
});
3 changes: 1 addition & 2 deletions ui/app/routes/vault/cluster/secrets/backend/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export default Ember.Route.extend(UnloadModel, {
templateName: 'vault/cluster/secrets/backend/credentials',

backendModel() {
const backend = this.paramsFor('vault.cluster.secrets.backend').backend;
return this.store.peekRecord('secret-engine', backend);
return this.modelFor('vault.cluster.secrets.backend');
},

pathQuery(role, backend) {
Expand Down
42 changes: 20 additions & 22 deletions ui/app/routes/vault/cluster/secrets/backend/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,29 @@ export default Ember.Route.extend({
model(params) {
const secret = params.secret ? params.secret : '';
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
const backendModel = this.modelFor('vault.cluster.secrets.backend');
return Ember.RSVP.hash({
secret,
secrets: this.store
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
id: secret,
backend,
responsePath: 'data.keys',
page: params.page,
pageFilter: params.pageFilter,
size: 100,
})
.then(model => {
this.set('has404', false);
return model;
})
.catch(err => {
if (backends.includes(backend) && err.httpStatus === 404 && secret === '') {
return [];
} else {
throw err;
}
}),
.lazyPaginatedQuery(this.getModelType(backend, params.tab), {
id: secret,
backend,
responsePath: 'data.keys',
page: params.page,
pageFilter: params.pageFilter,
size: 100,
})
.then(model => {
this.set('has404', false);
return model;
})
.catch(err => {
if (backendModel && err.httpStatus === 404 && secret === '') {
return [];
} else {
throw err;
}
})
});
},

Expand Down Expand Up @@ -138,11 +138,9 @@ export default Ember.Route.extend({
error(error, transition) {
const { secret } = this.paramsFor(this.routeName);
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');

Ember.set(error, 'secret', secret);
Ember.set(error, 'isRoot', true);
Ember.set(error, 'hasBackend', backends.includes(backend));
Ember.set(error, 'backend', backend);
const hasModel = this.controllerFor(this.routeName).get('hasModel');
// only swallow the error if we have a previous model
Expand Down
12 changes: 5 additions & 7 deletions ui/app/routes/vault/cluster/secrets/backend/secret-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import UnloadModelRoute from 'vault/mixins/unload-model-route';
export default Ember.Route.extend(UnloadModelRoute, {
capabilities(secret) {
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
let backendModel = this.store.peekRecord('secret-engine', backend);
let backendModel = this.modelFor('vault.cluster.secrets.backend');
let backendType = backendModel.get('type');
let version = backendModel.get('options.version');
let path;
Expand All @@ -21,8 +21,8 @@ export default Ember.Route.extend(UnloadModelRoute, {
return this.store.findRecord('capabilities', path);
},

backendType(path) {
return this.store.peekRecord('secret-engine', path).get('type');
backendType() {
return this.modelFor('vault.cluster.secrets.backend').get('type');
},

templateName: 'vault/cluster/secrets/backend/secretEditLayout',
Expand Down Expand Up @@ -50,7 +50,7 @@ export default Ember.Route.extend(UnloadModelRoute, {
aws: 'role-aws',
pki: secret && secret.startsWith('cert/') ? 'pki-certificate' : 'role-pki',
};
let backendModel = this.store.peekRecord('secret-engine', backend);
let backendModel = this.modelFor('vault.cluster.secrets.backend', backend);
let defaultType = 'secret';
if (backendModel.get('type') === 'kv' && backendModel.get('options.version') === 2) {
defaultType = 'secret-v2';
Expand Down Expand Up @@ -81,7 +81,7 @@ export default Ember.Route.extend(UnloadModelRoute, {
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const preferAdvancedEdit =
this.controllerFor('vault.cluster.secrets.backend').get('preferAdvancedEdit') || false;
const backendType = this.backendType(backend);
const backendType = this.backendType();
model.secret.setProperties({ backend });
controller.setProperties({
model: model.secret,
Expand All @@ -105,10 +105,8 @@ export default Ember.Route.extend(UnloadModelRoute, {
error(error) {
const { secret } = this.paramsFor(this.routeName);
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
const backends = this.modelFor('vault.cluster.secrets').mapBy('id');
Ember.set(error, 'keyId', backend + '/' + secret);
Ember.set(error, 'backend', backend);
Ember.set(error, 'hasBackend', backends.includes(backend));
return true;
},

Expand Down
3 changes: 1 addition & 2 deletions ui/app/routes/vault/cluster/secrets/backend/sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ export default Ember.Route.extend(UnloadModel, {
templateName: 'vault/cluster/secrets/backend/sign',

backendModel() {
const backend = this.paramsFor('vault.cluster.secrets.backend').backend;
return this.store.peekRecord('secret-engine', backend);
return this.modelFor('vault.cluster.secrets.backend');
},

pathQuery(role, backend) {
Expand Down
7 changes: 7 additions & 0 deletions ui/app/routes/vault/cluster/secrets/backends.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Ember from 'ember';

export default Ember.Route.extend({
model() {
return this.store.query('secret-engine', {});
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const CONFIGURABLE_BACKEND_TYPES = ['aws', 'ssh', 'pki'];
export default Ember.Route.extend({
model() {
const { backend } = this.paramsFor(this.routeName);
return this.store.query('secret-engine', {}).then(() => {
const model = this.store.peekRecord('secret-engine', backend);
return this.store.query('secret-engine', { path: backend }).then(modelList => {
let model = modelList && modelList.get('firstObject');
if (!model || !CONFIGURABLE_BACKEND_TYPES.includes(model.get('type'))) {
const error = new DS.AdapterError();
Ember.set(error, 'httpStatus', 404);
Expand Down
12 changes: 11 additions & 1 deletion ui/app/serializers/secret-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ export default DS.RESTSerializer.extend({
} else if (isQueryRecord) {
backends = this.normalizeBackend(null, payload);
} else {
backends = Object.keys(payload.data).map(id => this.normalizeBackend(id, payload[id]));
// this is terrible, I'm sorry
// TODO extract AWS and SSH config saving from the secret-engine model to simplify this
if (payload.data.secret) {
backends = Object.keys(payload.data.secret).map(id =>
this.normalizeBackend(id, payload.data.secret[id])
);
} else if (!payload.data.path) {
backends = Object.keys(payload.data).map(id => this.normalizeBackend(id, payload[id]));
} else {
backends = [this.normalizeBackend(payload.data.path, payload.data)];
}
}

const transformedPayload = { [primaryModelClass.modelName]: backends };
Expand Down
6 changes: 3 additions & 3 deletions ui/app/templates/components/ttl-picker.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
value={{time}}
id="time-{{elementId}}"
type="text"
name="time-{{elementId}}"
name="time"
class="input"
oninput={{action 'changedValue' 'time'}}
/>
Expand All @@ -15,8 +15,8 @@
<div class="select is-fullwidth">
<select
data-test-ttl-unit
name="unit-{{elementId}}"
id="unit-{{elementId}}"
name="unit"
id="unit"
onchange={{action 'changedValue' 'unit'}}
>
{{#each unitOptions as |unitOption|}}
Expand Down
11 changes: 0 additions & 11 deletions ui/app/templates/vault/cluster/error.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,6 @@
<p>
Make sure the policy for the path <code>{{model.policyPath}}</code> includes <code>capabilities = ['update']</code>.
</p>
{{else if (and
(eq model.httpStatus 403)
(eq model.policyPath 'sys/mounts')
)
}}
<p data-test-sys-mounts-warning>
Your auth token does not have access to {{model.policyPath}}. This is necessary in order to browse secret backends.
</p>
<p>
Make sure the policy for the path <code>{{model.policyPath}}</code> has <code>capabilities = ['list', 'read']</code>.
</p>
{{else}}
{{#if model.message}}
<p>{{model.message}}</p>
Expand Down
8 changes: 4 additions & 4 deletions ui/app/templates/vault/cluster/secrets/backend/error.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<header class="page-header">
<nav class="breadcrumb">
<li>
<a href={{href-to params=(if model.hasBackend
<a href={{href-to params=(if model.backend
(array "vault.cluster.secrets.backend.list-root")
(array "vault.cluster.secrets")
)
Expand Down Expand Up @@ -30,9 +30,9 @@
{{#if (eq model.httpStatus 404)}}
<p data-test-secret-not-found>
Unable to find secret at <code>{{concat model.backend "/" model.secret}}</code>. Try going back to the
{{#link-to params=(if model.hasBackend
(reduce-to-array "vault.cluster.secrets.backend.list-root")
(reduce-to-array "vault.cluster.secrets")
{{#link-to params=(if model.backend
(array "vault.cluster.secrets.backend.list-root")
(array "vault.cluster.secrets")
)
}}root{{/link-to}}
and navigating from there.
Expand Down
7 changes: 4 additions & 3 deletions ui/app/templates/vault/cluster/secrets/backends.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
<div class="level is-mobile">
<div class="level-left">
<div>
<a data-test-secret-path href={{href-to 'vault.cluster.secrets.backend.list-root' backend.id}} class="has-text-black has-text-weight-semibold">
{{i-con glyph="folder" size=14 class="has-text-grey-light"}}{{backend.path}}
</a>
<a data-test-secret-path
href={{href-to 'vault.cluster.secrets.backend.list-root' backend.id}}
class="has-text-black has-text-weight-semibold"
>{{i-con glyph="folder" size=14 class="has-text-grey-light"}}{{backend.path}}</a>
<br />
<span class="tag">
<code>
Expand Down
3 changes: 3 additions & 0 deletions ui/config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ module.exports = function(environment) {
ENV.contentSecurityPolicyMeta = true;
ENV.contentSecurityPolicy = {
'connect-src': ["'self'"],
'img-src': ["'self'", 'data:'],
'form-action': ["'none'"],
'script-src': ["'self'"],
'style-src': ["'unsafe-inline'", "'self'"],
};
}
Expand Down
31 changes: 0 additions & 31 deletions ui/tests/acceptance/secrets/no-access-test.js

This file was deleted.

Loading

0 comments on commit 1489293

Please sign in to comment.